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

/// @file
///
/// This file contains the definitions of the front-end to analyze the
/// BTF information contained in an ELF file.

#include "abg-internal.h"

#ifdef WITH_BTF

#include <bpf/btf.h>
#include <iostream>
#include <unordered_map>

#include "abg-elf-helpers.h"
#include "abg-ir-priv.h"

// <headers defining libabigail's API go under here>
ABG_BEGIN_EXPORT_DECLARATIONS

#include "abg-btf-reader.h"
#include "abg-ir.h"
#include "abg-tools-utils.h"

ABG_END_EXPORT_DECLARATIONS
// </headers defining libabigail's API>

namespace abigail
{
using namespace ir;

/// Namespace of the reader for the BTF debug information.
namespace btf
{

class reader;

/// A convenience typedef for a shared pointer to
/// abigail::btf::reader.
typedef shared_ptr<reader> reader_sptr;

static const char*
btf_offset_to_string(const ::btf* btf, uint32_t offset)
{
  if (!offset)
    return "__anonymous__";
  return btf__name_by_offset(btf, offset) ?: "(invalid string offset)";
}

/// A convenience typedef of a map that associates a btf type to a
/// libabigail ABI artifact.  The type is allocated inside a given btf
/// handle (of type ::btf*).  All handles (one for each kernel binary)
/// should be kept around until a complete corpus group is built.
typedef std::unordered_map<const btf_type*, type_or_decl_base_sptr>
btf_type_to_abi_artifact_map_type;

/// The BTF front-end abstraction type.
///
/// Note that one instance of front-end is meant to analyze one
/// vmlinux file and all its associated modules.  For now, the
/// front-end doesn't know how to analyze a module without having
/// analyzed a vmlinux first.
///
/// The BTF information of a vmlinux is parsed with the btf__parse
/// function.  The result is called a "base BTF" handle.
///
/// The BTF information of a kernel module is parsed with the function
/// btf__parse_split.  The result is called a "split BTF" handle.  A
/// split BTF handle references information that are in the base BTF
/// handle.  The base BTF handle can be retrieved from a split BTF
/// handle using btf__base_btf.
class reader : public elf_based_reader
{
  ::btf*				base_btf_handle_	= nullptr;
  // The path to the binary that contains the base BTF information
  // held in base_btf_handle_
  string				base_btf_file_name_;
  ::btf*				split_btf_handle_	= nullptr;
  // The path to the binary that contains the split BTF information
  // held in split_btf_handle_
  string				split_btf_file_name_;
  // A vector of (split) BTF objects that are to be freed once the
  // corpus group is built for an entire kernel (vmliunx + modules).
  vector<::btf*>			split_btfs_to_free_;
  translation_unit_sptr		cur_tu_;
  vector<type_base_sptr>		types_to_canonicalize_;
  btf_type_to_abi_artifact_map_type	btf_type_to_artifacts_;

  /// Getter of the "do_log" flag.
  ///
  /// This flag tells if we should log about various internal
  /// details.
  ///
  /// return the "do_log" flag.
  bool
  do_log() const
  {return options().do_log;}

  /// Setter of the "do_log" flag.
  ///
  /// This flag tells if we should log about various internal details.
  ///
  /// @param f the new value of the flag.
  void
  do_log(bool f)
  {options().do_log = f;}

  /// Getter of the "show_stats" flag.
  ///
  /// This flag tells if we should emit statistics about various
  /// internal stuff.
  ///
  /// @return the value of the flag.
  bool
  show_stats() const
  {return options().show_stats;}

  /// Setter of the "show_stats" flag.
  ///
  /// This flag tells if we should emit statistics about various
  /// internal stuff.
  ///
  /// @param f the value of the flag.
  void
  show_stats(bool f)
  {options().show_stats = f;}

  /// Getter of the handle to the base BTF object of the current
  /// binary being analyzed.
  ///
  /// The base BTF object ALWAYS represents the BTF information of the
  /// vmlinux binary, even if the current binary being analyzed is a
  /// kernel module.
  ///
  /// @return handle to the base BTF object of the current binary
  /// being analyzed.
  ::btf*
  base_btf_handle()
  {
    if (base_btf_handle_ == nullptr)
      {
	base_btf_handle_ = btf__parse(corpus_path().c_str(), nullptr);
	if (!base_btf_handle_)
	  {
	    std::cerr << "Could not parse base BTF information from file '"
		      << corpus_path().c_str() << "'" << std::endl;
	    return nullptr;
	  }
	base_btf_file_name_ = corpus_path();
      }
    return base_btf_handle_;
  }

  /// Read the BTF information of the current binary which path is
  /// @ref fe_iface::corpus_path() and return its associated object
  /// handle.  This is called the split BTF object.
  ///
  /// Note that this function expects the base BTF object (the one for
  /// the vmlinux binary) to be already present, otherwise, it returns
  /// nullptr.
  ///
  /// @return the split BTF object for the file designed by
  /// fe_iface::corpus_path().
  ::btf*
  read_split_btf()
  {
    if (!base_btf_handle_)
      {
	std::cerr << "Base BTF information not present.  "
		  << "Not attempting to parse split BTF information"
		  << std::endl;
	return nullptr;
      }

    if (corpus_path().empty() || corpus_path() == base_btf_file_name_)
      {
	std::cerr << "BTF reader not initialized with split file name.  "
		  << "Not attending to read split BTF information"
		  << std::endl;
	return nullptr;
      }

    split_btf_handle_ = btf__parse_split(corpus_path().c_str(),
					 base_btf_handle());
    if (!split_btf_handle_)
      {
	std::cerr << "Could not read split BTF information from file "
		  << corpus_path() << std::endl;
	return nullptr;
      }
    split_btf_file_name_ = corpus_path();

    return split_btf_handle_;
  }

  /// Getter of the handle to the BTF object as returned by libbpf.
  ///
  /// This returns the handle to the current BTF object.  If the
  /// current BTF object is for a vmlinux binary, then it's the base
  /// BTF object that is returned.  Otherwise, if the current BTF
  /// object if for a kernel module then it's the split BTF object
  /// that is returned.
  ///
  /// @return the handle to the BTF object of the current binary being
  /// analyeed by this front-end.
  ::btf*
  btf_handle()
  {
    if (split_btf_handle_)
      return split_btf_handle_;

    if (!base_btf_handle_)
      return base_btf_handle();

    if (corpus_path() != base_btf_file_name_)
      // The reader was re-initialized with a corpus_path that is
      // different from the the BTF base file.  That means we are
      // instructed to read a split BTF file information.
      return read_split_btf();

    return base_btf_handle();
  }

  /// Getter of the environment of the current front-end.
  ///
  /// @return The environment of the current front-end.
  environment&
  env()
  {return options().env;}

  /// Getter of the environment of the current front-end.
  ///
  /// @return The environment of the current front-end.
  const environment&
  env() const
  {return const_cast<reader*>(this)->env();}

  /// Getter of the current translation unit being built.
  ///
  /// Actually, BTF doesn't keep track of the translation unit each
  /// ABI artifact originates from.  So an "artificial" translation
  /// unit is built.  It contains all the ABI artifacts of the binary.
  ///
  /// @return The current translation unit being built.
  translation_unit_sptr&
  cur_tu()
  {return cur_tu_;}

  /// Getter of the current translation unit being built.
  ///
  /// Actually, BTF doesn't keep track of the translation unit each
  /// ABI artifact originates from.  So an "artificial" translation
  /// unit is built.  It contains all the ABI artifacts of the binary.
  ///
  /// @return The current translation unit being built.
  const translation_unit_sptr&
  cur_tu() const
  {return cur_tu_;}

  /// Getter of the current translation unit being built.
  ///
  /// Actually, BTF doesn't keep track of the translation unit each
  /// ABI artifact originates from.  So an "artificial" translation
  /// unit is built.  It contains all the ABI artifacts of the binary.
  ///
  /// @return The current translation unit being built.
  void
  cur_tu(const translation_unit_sptr& tu)
  {cur_tu_ = tu;}

  /// Getter of the map that associates a BTF type to the internal
  /// representation of an ABI artifact.
  ///
  /// @return The map that associates a BTF type to the IR of an ABI
  /// artifact.
  btf_type_to_abi_artifact_map_type&
  btf_type_to_artifacts()
  {return btf_type_to_artifacts_;}

  /// Getter of the map that associates a BTF type to the IR of an ABI
  /// artifact.
  ///
  /// @return The map that associates a BTF type to the IR of an ABI
  /// artifact.
  const btf_type_to_abi_artifact_map_type&
  btf_type_to_artifacts() const
  {return btf_type_to_artifacts_;}

  /// Get the IR of the ABI artifact that is associated to a given BTF
  /// type.
  ///
  /// If no ABI artifact is associated to the BTF type, then return
  /// nullptr.
  ///
  /// @return the ABI artifact that is associated to a given BTF type.
  type_or_decl_base_sptr
  lookup_artifact_from_btf_type(const btf_type* t)
  {
    auto i = btf_type_to_artifacts().find(t);
    if (i != btf_type_to_artifacts().end())
      return i->second;
    return type_or_decl_base_sptr();
  }

  /// Associate an ABI artifact to a given BTF type.
  ///
  /// @param artifact the ABI artifact to consider.
  ///
  /// @param btf_type_id the BTF type to associate to @p artifact.
  void
  associate_artifact_to_btf_type(const type_or_decl_base_sptr& artifact,
				    const btf_type* t)
  {btf_type_to_artifacts()[t] = artifact;}

  /// Schecule a type for canonicalization at the end of the debug
  /// info loading.
  ///
  /// @param t the type to schedule.
  void
  schedule_type_for_canonicalization(const type_base_sptr& t)
  {
    if (t && !t->get_naked_canonical_type())
      types_to_canonicalize_.push_back(t);
  }

  /// Canonicalize all the types scheduled for canonicalization using
  /// abigail::ir::canonicalize_types() which performs some sanity
  /// checking around type canonicalization if necessary.
  void
  canonicalize_types()
  {
    tools_utils::timer cn_timer;
    if (do_log())
      {
	std::cerr << "BTF Reader is going to canonicalize "
	     << std::dec
	     << types_to_canonicalize_.size()
	     << " types";
	corpus_sptr c = corpus();
	if (c)
	  std::cerr << " from corpus " << corpus()->get_path() << "\n";
	cn_timer.start();
      }

    ir::hash_and_canonicalize_types(types_to_canonicalize_.begin(),
				    types_to_canonicalize_.end(),
				    [](const vector<type_base_sptr>::const_iterator& i)
				    {return *i;}, do_log(), show_stats());

    if (do_log())
      {
	cn_timer.stop();
	std::cerr << "BTF Reader finished types "
	     << "sorting, hashing & canonicalizing in: "
	     << cn_timer << "\n";
      }

  }

  /// Getter of the number of types carried by a given BTF object.
  ///
  /// @param handle the BTF object to consider.
  ///
  /// @return the number of types carried by a given BTF object.
  uint64_t
  nr_btf_types(const ::btf* handle) const
  {
#ifdef WITH_BTF__GET_NR_TYPES
#define GET_NB_TYPES btf__get_nr_types
#endif

#ifdef WITH_BTF__TYPE_CNT
#undef GET_NB_TYPES
#define GET_NB_TYPES btf__type_cnt
#endif

#ifndef GET_NB_TYPES
    ABG_ASSERT_NOT_REACHED;
    return 0;
#endif

    return GET_NB_TYPES(handle);
  }

protected:
  reader() = delete;

  /// Initializer of the current instance of @ref btf::reader.
  ///
  /// This frees the resources used by the current instance of @ref
  /// btf::reader and gets it ready to analyze another ELF
  /// file.
  ///
  /// @param elf_path the path to the ELF file to read from.
  ///
  /// @param debug_info_root_paths the paths where to look for
  /// seperate debug info.
  ///
  /// @param load_all_types if true, then load all the types described
  /// in the binary, rather than loading only the types reachable from
  /// the exported decls.
  ///
  /// @param linux_kernel_mode
  void
  initialize(const string&		elf_path,
	     const vector<string>&	debug_info_root_paths,
	     bool			load_all_types,
	     bool			linux_kernel_mode)
  {
    if (split_btf_handle_)
      {
	// We need to keep this split_btf_handle_ on the side so that
	// we can free it when we are done analyzing all the kernel
	// modules.  We cannot free it right now because the memory of
	// all btf types lives in it.
	split_btfs_to_free_.push_back(split_btf_handle_);
	split_btf_handle_ = nullptr;
      }

    split_btf_file_name_.clear();
    types_to_canonicalize_.clear();
    cur_tu_.reset();
    elf_based_reader::initialize(elf_path, debug_info_root_paths);
    corpus_path(elf_path);
    options().load_all_types = load_all_types;
    options().load_in_linux_kernel_mode = linux_kernel_mode;
  }

  /// Constructor of the btf::reader type.
  ///
  /// @param elf_path the path to the ELF file to analyze.
  ///
  /// @param debug_info_root_paths the set of directory where to look
  /// debug info from, for cases where the debug is split.
  ///
  /// @param environment the environment of the current front-end.
  ///
  /// @param load_all_types if true load all the types described by
  /// the BTF debug info, as opposed to loading only the types
  /// reachable from the decls that are defined and exported.
  ///
  /// @param linux_kernel_mode if true, then consider the binary being
  /// analyzed as a linux kernel binary.
  reader(const string&		elf_path,
	 const vector<string>&	debug_info_root_paths,
	 environment&		environment,
	 bool			load_all_types,
	 bool			linux_kernel_mode)
    : elf_based_reader(elf_path,
		       debug_info_root_paths,
		       environment)
  {
    initialize(elf_path, debug_info_root_paths,
	       load_all_types, linux_kernel_mode);
  }

public:

  /// Constructor of the btf::reader type.
  ///
  /// @param elf_path the path to the ELF file to analyze.
  ///
  /// @param debug_info_root_paths the set of directory where to look
  /// debug info from, for cases where the debug is split.
  ///
  /// @param environment the environment of the current front-end.
  ///
  /// @param load_all_types if true load all the types described by
  /// the BTF debug info, as opposed to loading only the types
  /// reachable from the decls that are defined and exported.
  ///
  /// @param linux_kernel_mode if true, then consider the binary being
  /// analyzed as a linux kernel binary.
  static btf::reader_sptr
  create(const string&		elf_path,
	 const vector<string>&	debug_info_root_paths,
	 environment&		environment,
	 bool			load_all_types,
	 bool			linux_kernel_mode)
  {
    reader_sptr result(new reader(elf_path, debug_info_root_paths, environment,
				  load_all_types, linux_kernel_mode));
    return result;
  }

  /// Destructor of the btf::reader type.
  ~reader()
  {
    for (auto b : split_btfs_to_free_)
      btf__free(b);
    btf__free(split_btf_handle_);
    btf__free(base_btf_handle_);
    split_btf_handle_ = nullptr;
    base_btf_handle_ = nullptr;
  }

  /// Read the ELF information as well as the BTF type information to
  /// build an ABI corpus.
  ///
  /// @param status output parameter.  The status of the analysis.
  ///
  /// @return the resulting ABI corpus.
  corpus_sptr
  read_corpus(status& status)
  {
    // Read the properties of the ELF file.
    elf::reader::read_corpus(status);

    corpus::origin origin = corpus()->get_origin();
    origin |= corpus::BTF_ORIGIN;
    corpus()->set_origin(origin);
    if (corpus_group())
      {
	origin |= corpus_group()->get_origin();
	corpus_group()->set_origin(origin);
      }

    if ((status & STATUS_NO_SYMBOLS_FOUND)
	|| !(status & STATUS_OK))
      // Either we couldn't find ELF symbols or something went badly
      // wrong.  There is nothing we can do with this ELF file.  Bail
      // out.
      return corpus_sptr();

    if (find_btf_section() == nullptr)
      status |= STATUS_DEBUG_INFO_NOT_FOUND;

    read_debug_info_into_corpus();

    status |= STATUS_OK;

    return corpus();
  }

  /// Read the BTF debug info to construct the ABI corpus.
  ///
  /// @return the resulting ABI corpus.
  corpus_sptr
  read_debug_info_into_corpus()
  {
    if (!btf_handle())
      return corpus_sptr();

    translation_unit_sptr artificial_tu
      (new translation_unit(env(), "", /*address_size=*/64));
    corpus()->add(artificial_tu);
    cur_tu(artificial_tu);

#ifdef WITH_DEBUG_SELF_COMPARISON
    if (env().self_comparison_debug_is_on())
      {
	corpus_group_sptr g = corpus_group();
	if (g)
	  env().set_self_comparison_debug_input(g);
	else
	  env().set_self_comparison_debug_input(corpus());
      }
#endif

    int number_of_types = nr_btf_types(btf_handle());
    int first_type_id = 1;
    // Are we looking at the BTF for a kernel module?
    const ::btf* base = btf__base_btf(btf_handle());
    if (base)
      {
	// So, base is non-nil.  This means we are looking at the BTF
	// for a kernel module and base points to the BTF for the
	// corresponding vmlinux.  That base BTF should be the same as
	// base_btf_handle().
	ABG_ASSERT(base == base_btf_handle());

	// The ID of the first type that is contained in this BTF
	// representing a kernel module is the number of types
	// contained in the base BTF (i.e, the BTF for the vmlinux
	// binary).
	first_type_id = nr_btf_types(base);
      }

    // Let's cycle through whatever is described in the BTF section
    // and emit libabigail IR for it.
    for (int type_id = first_type_id;
	 type_id < number_of_types;
	 ++type_id)
      {
	// Build IR nodes only for decls (functions and variables)
	// that have associated ELF symbols that are publicly defined
	// and exported, unless the user asked to load all types.

	bool do_construct_ir_node = false;

	const btf_type* t = btf__type_by_id(btf_handle(), type_id);
	string name;
	if (t->name_off)
	  name = btf_offset_to_string(btf_handle(), t->name_off);

	int kind = btf_kind(t);
	if (kind == BTF_KIND_FUNC)
	  {
	    ABG_ASSERT(!name.empty());
	    if (btf_vlen(t) == BTF_FUNC_GLOBAL
		|| btf_vlen(t) == BTF_FUNC_EXTERN
		|| function_symbol_is_exported(name))
	      do_construct_ir_node = true;
	  }
	else if (kind == BTF_KIND_VAR)
	  {
	    ABG_ASSERT(!name.empty());
	    if (btf_vlen(t) == BTF_VAR_GLOBAL_ALLOCATED
		|| btf_vlen(t) == BTF_VAR_GLOBAL_EXTERN
		|| variable_symbol_is_exported(name))
	      do_construct_ir_node = true;
	  }
	else if (options().load_all_types)
	  do_construct_ir_node = true;

	if (do_construct_ir_node)
	  build_ir_node_from_btf_type(type_id);
      }

    canonicalize_types();
    corpus()->sort_functions();
    corpus()->sort_variables();
    return corpus();
  }

  /// Build an abigail IR node for a given type described by a BTF
  /// type ID.  The node is added to the ABI corpus.
  ///
  /// @param type_id the ID of the type to build and IR node for.
  ///
  /// @return the IR node representing the type @p type_id.
  type_or_decl_base_sptr
  build_ir_node_from_btf_type(int type_id)
  {
    type_or_decl_base_sptr result;
    const btf_type *t = nullptr;

    t = btf__type_by_id(btf_handle(), type_id);

    if ((result = lookup_artifact_from_btf_type(t)))
      return result;

    if (!result)
      {
	ABG_ASSERT(t);
	int type_kind = btf_kind(t);

	switch(type_kind)
	  {
	  case BTF_KIND_UNKN/* Unknown: This is really for the void
			       type. */:
	    result = build_ir_node_for_void_type();
	    break;

	  case BTF_KIND_INT/* Integer */:
	    result = build_int_type(type_id);
	    break;

	  case BTF_KIND_FLOAT/* Floating point */:
	    result = build_float_type(type_id);
	    break;

	  case BTF_KIND_TYPEDEF/* Typedef*/:
	    result = build_typedef_type(type_id);
	    break;

	  case BTF_KIND_PTR/* Pointer */:
	    result = build_pointer_type(type_id);
	    break;

	  case BTF_KIND_ARRAY/* Array */:
	    result = build_array_type(type_id);
	    break;

	  case BTF_KIND_ENUM/* Enumeration up to 32-bit values */:
#ifdef WITH_BTF_ENUM64
	  case BTF_KIND_ENUM64/* Enumeration up to 64-bit values */:
#endif
	    result = build_enum_type(type_id);
	    break;

	  case BTF_KIND_STRUCT/* Struct */:
	  case BTF_KIND_UNION/* Union */:
	    result = build_class_or_union_type(type_id);
	    break;

	  case BTF_KIND_FWD/* Forward */:
	    result = build_class_or_union_type(type_id);
	    break;

	  case BTF_KIND_CONST/* Const	*/:
	  case BTF_KIND_VOLATILE/* Volatile */:
	  case BTF_KIND_RESTRICT/* Restrict */:
	    result = build_qualified_type(type_id);
	    break;

	  case BTF_KIND_FUNC/* Function */:
	    result = build_function_decl(type_id);
	    break;

	  case BTF_KIND_FUNC_PROTO/* Function Proto */:
	    result = build_function_type(type_id);
	    break;

	  case BTF_KIND_VAR/* Variable */:
	    result = build_variable_decl(type_id);
	    break;

#ifdef WITH_BTF_KIND_TYPE_TAG
	  case BTF_KIND_TYPE_TAG/* Type Tag */:
	    break;
#endif
#ifdef WITH_BTF_KIND_DECL_TAG
	  case BTF_KIND_DECL_TAG/* Decl Tag */:
	    break;
#endif
	  case BTF_KIND_DATASEC/* Section */:
	    break;
	  default:
	    ABG_ASSERT_NOT_REACHED;
	    break;
	  }
      }

    add_decl_to_scope(is_decl(result), cur_tu()->get_global_scope());

    if (type_base_sptr type = is_type(result))
      schedule_type_for_canonicalization(type);

    associate_artifact_to_btf_type(result, t);

    if (function_decl_sptr fn = is_function_decl(result))
      add_fn_to_exported_or_undefined_decls(fn.get());
    else if (var_decl_sptr var = is_var_decl(result))
      add_var_to_exported_or_undefined_decls(var);

    return result;
  }

  /// Build an IR node for the "void" type.
  ///
  /// @return the IR node for the void type.
  type_base_sptr
  build_ir_node_for_void_type()
  {
    type_base_sptr t = env().get_void_type();
    add_decl_to_scope(is_decl(t), cur_tu()->get_global_scope());
    schedule_type_for_canonicalization(t);
    return t;
  }

  /// Build an IR node for the "void" type.
  ///
  /// @return the IR node for the void type.
  type_base_sptr
  build_ir_node_for_void_pointer_type()
  {
    type_base_sptr t = env().get_void_pointer_type();
    add_decl_to_scope(is_decl(t), cur_tu()->get_global_scope());
    schedule_type_for_canonicalization(t);
    return t;
  }

  /// Build an IR node for the "variadic parameter" type.
  ///
  /// @return the IR node for the "variadic parameter" type.
  type_base_sptr
  build_ir_node_for_variadic_parameter_type()
  {
    type_base_sptr t = env().get_variadic_parameter_type();
    add_decl_to_scope(is_decl(t), cur_tu()->get_global_scope());
    decl_base_sptr t_decl = get_type_declaration(t);
    schedule_type_for_canonicalization(t);
    return t;
  }

  /// Build an IR node for an integer type expressed in BTF.
  ///
  /// @param t a pointer a BTF type describing an integer.
  ///
  /// @return a pointer to @ref type_decl representing an integer
  /// type.
  type_or_decl_base_sptr
  build_int_type(int type_id)
  {
    type_decl_sptr result;

    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    ABG_ASSERT(btf_kind(t) == BTF_KIND_INT);

    uint32_t info = *reinterpret_cast<const uint32_t*>(t + 1);
    uint64_t byte_size = 0, bit_size = 0;
    string type_name;

    byte_size = t->size;
    bit_size = byte_size * 8;

    if (BTF_INT_ENCODING(info) & BTF_INT_CHAR)
      {
	if (!(BTF_INT_ENCODING(info) & BTF_INT_SIGNED))
	  type_name = "unsigned ";
	type_name += "char";
      }
    else if (BTF_INT_ENCODING(info) & BTF_INT_BOOL)
      type_name = "bool";
    else if (!(BTF_INT_ENCODING(info) & BTF_INT_SIGNED))
      {
	type_name = "unsigned ";
	type_name += btf_offset_to_string(btf_handle(), t->name_off);
      }
    else
      type_name = btf_offset_to_string(btf_handle(), t->name_off);

    location loc;
    result.reset(new type_decl(env(), type_name,
			       bit_size, /*alignment=*/0,
			       loc, type_name));

    return result;
  }

  /// Build an IR node for a float type expressed in BTF.
  ///
  /// @return a pointer to @ref type_decl representing a float type.
  type_or_decl_base_sptr
  build_float_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    ABG_ASSERT(btf_kind(t) == BTF_KIND_FLOAT);

    string type_name = btf_offset_to_string(btf_handle(), t->name_off);;
    uint64_t byte_size = t->size, bit_size = byte_size * 8;
    location loc;
    type_decl_sptr result(new type_decl(env(), type_name, bit_size,
					/*alignment=*/0, loc, type_name));

    return result;
  }

  /// Build an IR type that represents the underlying type of an enum type.
  ///
  /// This is a sub-routine of the build_enum_type() function.
  ///
  /// @param enum_name the name of the enum type this type is an
  /// underlying type for.
  ///
  /// @param enum_size the size of the enum.
  ///
  /// @param is_anonymous if true, the enum type is anonymous.
  ///
  /// @return a pointer to type_decl that represents a integer type
  /// that is the underlying type of an enum type.
  type_decl_sptr
  build_enum_underlying_type(const string enum_name, uint64_t enum_size,
			     bool is_anonymous = true)
  {
    string underlying_type_name =
      build_internal_underlying_enum_type_name(enum_name,
					       is_anonymous,
					       enum_size);
    type_decl_sptr result(new type_decl(env(), underlying_type_name,
					enum_size, enum_size, location()));
    result->set_is_anonymous(is_anonymous);
    result->set_is_artificial(true);
    add_decl_to_scope(result, cur_tu()->get_global_scope());
    schedule_type_for_canonicalization(result);
    return result;
  }

  /// Build an IR node that represents an enum type expressed in BTF.
  ///
  /// @param type_id the ID of the BTF representation of the enum.
  ///
  /// @return a pointer to @ref enum_type_decl representing @p t.
  type_or_decl_base_sptr
  build_enum_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
#ifdef WITH_BTF_ENUM64
    ABG_ASSERT(kind == BTF_KIND_ENUM || kind == BTF_KIND_ENUM64);
#else
    ABG_ASSERT(kind == BTF_KIND_ENUM);
#endif

    int byte_size = t->size, bit_size = byte_size * 8;

    string enum_name;
    if (t->name_off)
      enum_name = btf_offset_to_string(btf_handle(), t->name_off);
    bool is_anonymous = enum_name.empty();

    int num_enms = btf_vlen(t);
    enum_type_decl::enumerators enms;
    string e_name;
    if (kind == BTF_KIND_ENUM)
      {
	const struct btf_enum* e = btf_enum(t);
	uint32_t e_value = 0;
	for (int i = 0; i < num_enms; ++i, ++e)
	  {
	    e_name = btf_offset_to_string(btf_handle(), e->name_off);
	    e_value = e->val;
	    enms.push_back(enum_type_decl::enumerator(e_name, e_value));
	  }
      }
#ifdef WITH_BTF_ENUM64
    else if (kind == BTF_KIND_ENUM64)
      {
	const struct btf_enum64* e =
	  reinterpret_cast<const struct btf_enum64*>(t + 1);
	uint64_t e_value = 0;
	for (int i = 0; i < num_enms; ++i, ++e)
	  {
	    e_name = btf_offset_to_string(btf_handle(), e->name_off);
	    e_value = (static_cast<uint64_t>(e->val_hi32) << 32) | e->val_lo32;
	    enms.push_back(enum_type_decl::enumerator(e_name, e_value));
	  }
      }
#endif
    else
      ABG_ASSERT_NOT_REACHED;

    type_decl_sptr underlying_type =
      build_enum_underlying_type(enum_name, bit_size, is_anonymous);
    enum_type_decl_sptr result(new enum_type_decl(enum_name,
						  location(),
						  underlying_type,
						  enms, enum_name));
    result->set_is_anonymous(is_anonymous);
    return result;
  }

  /// Build an IR node for a typedef that is expressed in BTF.
  ///
  /// @param type_id the ID of the BTF representation of a typedef.
  ///
  /// @return a pointer to @ref typedef_decl representing @p t.
  type_or_decl_base_sptr
  build_typedef_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_TYPEDEF);

    string type_name = btf_offset_to_string(btf_handle(), t->name_off);
    type_base_sptr underlying_type =
      is_type(build_ir_node_from_btf_type(t->type));
    if (!underlying_type)
      return type_or_decl_base_sptr();

    typedef_decl_sptr result(new typedef_decl(type_name, underlying_type,
					      location(),
					      /*linkage_name=*/type_name));
    if ((is_class_or_union_type(underlying_type)
	 || is_enum_type(underlying_type))
	&& is_anonymous_type(underlying_type))
      get_type_declaration(underlying_type)->set_naming_typedef(result);

    return result;
  }

  /// Build an IR node representing a pointer described in BTF.
  ///
  /// @param type_id the ID of a BTF representation of a pointer type.
  ///
  /// @return a pointer to pointer_type_def that represents @p t.
  type_or_decl_base_sptr
  build_pointer_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_PTR);

    type_base_sptr underlying_type =
      is_type(build_ir_node_from_btf_type(t->type));
    if (!underlying_type)
      return type_or_decl_base_sptr();
    if (env().is_void_type(underlying_type))
      // Recognize a pointer to void and return a special unique IR
      // for it.
      return build_ir_node_for_void_pointer_type();

    int size = elf_helpers::get_architecture_word_size(elf_handle());
    size *= 8;
    pointer_type_def_sptr result(new pointer_type_def(underlying_type, size,
						      /*alignment=*/0,
						      location()));
    return result;
  }

  /// Build an IR node representing an array type described in BTF.
  ///
  /// @param type_id the ID of the BTF representation of an array
  /// type.
  ///
  /// return a pointer to @ref array_type_def representing @p t.
  type_or_decl_base_sptr
  build_array_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_ARRAY);

    const struct btf_array* arr = btf_array(t);

    type_base_sptr underlying_type =
      is_type(build_ir_node_from_btf_type(arr->type));
    if (!underlying_type)
      return type_or_decl_base_sptr();

    uint64_t lower_bound = 0;
    // Note that arr->nelems can be 0;
    uint64_t upper_bound = arr->nelems ? arr->nelems - 1: 0;

    array_type_def::subrange_sptr subrange(new array_type_def::subrange_type
					   (env(), /*name=*/"",
					    lower_bound, upper_bound,
					    location()));
    subrange->is_non_finite(!arr->nelems);
    subrange->set_size_in_bits(cur_tu()->get_address_size());
    add_decl_to_scope(subrange, cur_tu()->get_global_scope());
    schedule_type_for_canonicalization(subrange);
    array_type_def::subranges_type subranges = {subrange};
    array_type_def_sptr result(new array_type_def(underlying_type,
						  subranges, location()));

    return result;
  }

  /// Build an IR node representing a qualified type described in BTF.
  ///
  /// @param type_id the ID of the BTF representation of an array
  /// type.
  ///
  /// @return a pointer to a qualified_type_def representing @ t.
  type_or_decl_base_sptr
  build_qualified_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_CONST
	       || kind == BTF_KIND_VOLATILE
	       || kind == BTF_KIND_RESTRICT);

    type_base_sptr underlying_type =
      is_type(build_ir_node_from_btf_type(t->type));
    if (!underlying_type)
      return type_or_decl_base_sptr();

    qualified_type_def::CV qual = qualified_type_def::CV_NONE;
    if (kind == BTF_KIND_CONST)
      qual |= qualified_type_def::CV_CONST;
    else if (kind == BTF_KIND_VOLATILE)
      qual |= qualified_type_def::CV_VOLATILE;
    else if (kind == BTF_KIND_RESTRICT)
      qual |= qualified_type_def::CV_RESTRICT;
    else
      ABG_ASSERT_NOT_REACHED;

    qualified_type_def_sptr result(new qualified_type_def(underlying_type,
							  qual, location()));
    return result;
  }

  /// Build an IR node for a class or union type expressed in BTF.
  ///
  /// @param type_id the ID of a pointer to a BTF type describing a
  /// class or union type.
  ///
  /// @return a pointer to either a @ref class_decl or a @ref
  /// union_decl type representing the type expressed by @p t.
  type_or_decl_base_sptr
  build_class_or_union_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);

    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_STRUCT
	       || kind == BTF_KIND_UNION
	       || kind == BTF_KIND_FWD);

    string type_name;
    if (t->name_off)
      type_name = btf_offset_to_string(btf_handle(), t->name_off);

    bool is_anonymous = type_name.empty();
    uint64_t size = t->size;
    size *= 8;

    bool is_decl_only = (kind == BTF_KIND_FWD);

    class_or_union_sptr result;
    if (kind == BTF_KIND_STRUCT
	|| (kind == BTF_KIND_FWD
	    && BTF_INFO_KFLAG(t->info) == 0 /*struct*/))
    result.reset(new class_decl(env(), type_name, size,
				/*alignment=*/0,
				/*is_struct=*/true,
				location(),
				decl_base::VISIBILITY_DEFAULT,
				is_anonymous));
    else if (kind == BTF_KIND_UNION
	     || (kind == BTF_KIND_FWD
		 && BTF_INFO_KFLAG(t->info) == 1/*union*/))
      result.reset(new union_decl(env(), type_name, size, location(),
				  decl_base::VISIBILITY_DEFAULT,
				  is_anonymous));
    else
      ABG_ASSERT_NOT_REACHED;

    if (is_decl_only)
      result->set_is_declaration_only(is_decl_only);

    add_decl_to_scope(result, cur_tu()->get_global_scope());

    associate_artifact_to_btf_type(result, t);

    // For defined classes and unions, add data members to the type
    // being built.
    if (!is_decl_only)
      {
	const struct btf_member *m =
	  reinterpret_cast<const struct btf_member*>(t + 1);
	uint64_t nb_members = btf_vlen(t);

	for (uint64_t i = 0; i < nb_members; ++i, ++m)
	  {
	    type_base_sptr member_type =
	      is_type(build_ir_node_from_btf_type(m->type));
	    if (!member_type)
	      continue;

	    string member_name;
	    if (m->name_off)
	      member_name = btf_offset_to_string(btf_handle(), m->name_off);
	    var_decl_sptr data_member(new var_decl(member_name,
						   member_type,
						   location(),
						   /*linkage_name=*/""));
	    uint64_t offset_in_bits =
	      BTF_INFO_KFLAG(t->info)
	      ? BTF_MEMBER_BIT_OFFSET(m->offset)
	      : m->offset;

	    result->add_data_member(data_member,
				    public_access,
				    /*is_laid_out=*/true,
				    /*is_static=*/false,
				    offset_in_bits);
	  }
      }
    return result;
  }

  /// Build an IR node for a function type expressed in BTF.
  ///
  /// @param type_id the ID of a pointer to a BTF type describing a
  /// function type.
  ///
  /// @return a pointer to a @ref function_type representing the
  /// function type expressed by @p t.
  type_or_decl_base_sptr
  build_function_type(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_FUNC_PROTO);

    type_base_sptr return_type = is_type(build_ir_node_from_btf_type(t->type));
    if (return_type == nullptr)
      return type_or_decl_base_sptr();

    int address_size = elf_helpers::get_architecture_word_size(elf_handle());
    address_size *= 8;
    function_type_sptr result(new function_type(env(), address_size,
						/*alignment=*/0));
    result->set_return_type(return_type);

    associate_artifact_to_btf_type(result, t);

    uint16_t nb_parms = btf_vlen(t);
    const struct btf_param* parm =
      reinterpret_cast<const struct btf_param*>(t + 1);

    function_decl::parameters function_parms;
    for (uint16_t i = 0; i < nb_parms; ++i, ++parm)
      {
	type_base_sptr parm_type;
	string parm_name;
	bool is_variadic = false;

	if (parm->name_off == 0 && parm->type == 0)
	  {
	    is_variadic = true;
	    parm_type = build_ir_node_for_variadic_parameter_type();
	  }
	else
	  {
	    parm_name = btf_offset_to_string(btf_handle(), parm->name_off);
	    parm_type = is_type(build_ir_node_from_btf_type(parm->type));
	  }

	if (!parm_type)
	  continue;

	function_decl::parameter_sptr p
	  (new function_decl::parameter(parm_type, parm_name,
					location(), is_variadic));
	function_parms.push_back(p);
      }
    result->set_parameters(function_parms);

    cur_tu()->bind_function_type_life_time(result);

    return result;
  }

  /// Build an IR node for a function declaration expressed in BTF.
  ///
  /// @param type_id the ID of a pointer to a BTF "type" which realy
  /// describes a function declaration.
  ///
  /// @return a pointer to a @ref function_decl representing the
  /// function declaration expressed by @p t.
  type_or_decl_base_sptr
  build_function_decl(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_FUNC);

    function_decl_sptr result;

    string fn_name = btf_offset_to_string(btf_handle(), t->name_off);

    type_base_sptr fn_type = is_type(build_ir_node_from_btf_type(t->type));
    if (!fn_type)
      return result;

    result.reset(new function_decl(fn_name, fn_type, /*is_inline=*/false,
				   location(), /*linkage_name=*/fn_name));

    elf_symbol_sptr fn_sym;
    if ((fn_sym = function_symbol_is_exported(fn_name))
	|| (fn_sym = function_symbol_is_undefined(fn_name)))
      {
	result->set_symbol(fn_sym);
	if (fn_sym->is_defined())
	  result->set_is_in_public_symbol_table(true);
      }
    return result;
  }

  /// Build an IR node for a variable declaration expressed in BTF.
  ///
  /// @param t a pointer to a BTF "type" describing a variable
  /// declaration.
  ///
  /// @return a pointer to @ref var_decl representing the variable
  /// declaration expressed by @p t.
  type_or_decl_base_sptr
  build_variable_decl(int type_id)
  {
    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
    int kind = btf_kind(t);
    ABG_ASSERT(kind == BTF_KIND_VAR);

    var_decl_sptr result;

    string var_name = btf_offset_to_string(btf_handle(), t->name_off);

    type_base_sptr var_type = is_type(build_ir_node_from_btf_type(t->type));
    if (!var_type)
      return result;

    result.reset(new var_decl(var_name, var_type, location(),
			      /*linkage_name=*/var_name));

    elf_symbol_sptr var_sym;
    if ((var_sym = variable_symbol_is_exported(var_name))
	|| (var_sym = variable_symbol_is_undefined(var_name)))
      {
	result->set_symbol(var_sym);
	if (var_sym->is_defined())
	  result->set_is_in_public_symbol_table(true);
      }
    return result;
  }

}; // end class reader.

/// Create and return a BTF reader (or front-end) which is an instance
/// of @ref btf::reader.
///
/// @param elf_path the path to the path to the elf file the reader is
/// to be used for.
///
/// @param debug_info_root_paths a vector to the paths to the
/// directories under which the debug info is to be found for @p
/// elf_path.  Pass an empty vector if th debug info is not in a split
/// file.
///
/// @param environment the environment used by the current context.
/// This environment contains resources needed by the BTF reader and
/// by the types and declarations that are to be created later.  Note
/// that ABI artifacts that are to be compared all need to be created
/// within the same environment.
///
/// Please also note that the life time of this environment object
/// must be greater than the life time of the resulting @ref
/// reader the context uses resources that are allocated in the
/// environment.
///
/// @param load_all_types if set to false only the types that are
/// reachable from publicly exported declarations (of functions and
/// variables) are read.  If set to true then all types found in the
/// debug information are loaded.
///
/// @param linux_kernel_mode if set to true, then consider the special
/// linux kernel symbol tables when determining if a symbol is
/// exported or not.
///
/// @return a smart pointer to the resulting btf::reader.
elf_based_reader_sptr
create_reader(const std::string&	elf_path,
	      const vector<string>&	debug_info_root_paths,
	      environment&		env,
	      bool			load_all_types,
	      bool			linux_kernel_mode)
{
  reader_sptr rdr = reader::create(elf_path, debug_info_root_paths, env,
				   load_all_types, linux_kernel_mode);
  return rdr;
}

} // end namespace btf
} // end namespace abigail

#endif //WITH_BTF
