// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- Mode: C++ -*-
//
// Copyright (C) 2020-2025 Google, Inc.
//
// Author: Matthias Maennich

/// @file
///
/// This contains the declarations for the symtab reader.

#ifndef __ABG_SYMTAB_READER_H__
#define __ABG_SYMTAB_READER_H__

#include <gelf.h>

#include <functional>
#include <iterator>
#include <memory>
#include <unordered_map>
#include <vector>

#include "abg-cxx-compat.h"  // for abg_compat::optional
#include "abg-ir.h"

namespace abigail
{
namespace symtab_reader
{

/// The symtab filter is the object passed to the symtab object in order to
/// iterate over the symbols in the symtab while applying filters.
///
/// The general idea is that it consists of a set of optionally enforced flags,
/// such as 'functions' or 'variables'. If not set, those are not filtered for,
/// neither inclusive nor exclusive. If set they are all ANDed together.
class symtab_filter
{
public:
  // Default constructor disabling all features.
  symtab_filter() {}

  bool
  matches(const elf_symbol& symbol) const;

  /// Enable or disable function filtering
  ///
  /// @param new_value whether to filter for functions
  void
  set_functions(bool new_value = true)
  {functions_ = new_value;};

  /// Enable or disable variable filtering
  ///
  /// @param new_value whether to filter for variables
  void
  set_variables(bool new_value = true)
  {variables_ = new_value;};

  /// Enable or disable public symbol filtering
  ///
  /// @param new_value whether to filter for public symbols
  void
  set_public_symbols(bool new_value = true)
  {public_symbols_ = new_value;};

  /// Enable or disable undefined symbol filtering
  ///
  /// @param new_value whether to filter for undefined symbols
  void
  set_undefined_symbols(bool new_value = true)
  {undefined_symbols_ = new_value;};

  /// Enable or disable kernel symbol filtering
  ///
  /// @param new_value whether to filter for kernel symbols
  void
  set_kernel_symbols(bool new_value = true)
  {kernel_symbols_ = new_value;};

private:
  // The symbol is a function (FUNC)
  abg_compat::optional<bool> functions_;

  // The symbol is a variables (OBJECT)
  abg_compat::optional<bool> variables_;

  // The symbol is publicly accessible (global/weak with default/protected
  // visibility)
  abg_compat::optional<bool> public_symbols_;

  // The symbols is not defined (declared)
  abg_compat::optional<bool> undefined_symbols_;

  // The symbol is listed in the ksymtab (for Linux Kernel binaries).
  abg_compat::optional<bool> kernel_symbols_;
};

/// Base iterator for our custom iterator based on whatever the const_iterator
/// is for a vector of symbols.
/// As of writing this, std::vector<elf_symbol_sptr>::const_iterator.
using base_iterator = elf_symbols::const_iterator;

/// An iterator to walk a vector of elf_symbols filtered by symtab_filter.
///
/// The implementation inherits all properties from the vector's
/// const_iterator, but intercepts where necessary to allow effective
/// filtering. This makes it a STL compatible iterator for general purpose
/// usage.
class symtab_iterator : public base_iterator
{
public:
  using value_type = base_iterator::value_type;
  using reference = base_iterator::reference;
  using pointer = base_iterator::pointer;
  using difference_type = base_iterator::difference_type;
  using iterator_category = std::forward_iterator_tag;
  using iterator_concept = std::forward_iterator_tag;

  /// Construct the iterator based on a pair of underlying iterators and a
  /// symtab_filter object. Immediately fast forward to the next element that
  /// matches the criteria (if any).
  ///
  /// @param begin the underlying begin iterator
  ///
  /// @param begin the underlying end iterator
  ///
  /// @param filter the symtab_filter to apply
  symtab_iterator(base_iterator	       begin,
		  base_iterator	       end,
		  const symtab_filter& filter = symtab_filter())
    : base_iterator(begin), end_(end), filter_(filter)
  {skip_to_next();}

  /// Pre-increment operator to advance to the next matching element.
  ///
  /// @return itself after incrementing
  symtab_iterator&
  operator++()
  {
    base_iterator::operator++();
    skip_to_next();
    return *this;
  }

  /// Post-increment operator to advance to the next matching element.
  ///
  /// @return a copy of the iterator before incrementing
  symtab_iterator
  operator++(int)
  {
    symtab_iterator result(*this);
    ++(*this);
    return result;
  }

private:
  /// The end of the underlying iterator.
  const base_iterator end_;

  /// The symtab_filter used to determine when to advance.
  const symtab_filter& filter_;

  /// Skip to the next element that matches the filter criteria (if any). Hold
  /// off when reaching the end of the underlying iterator.
  void
  skip_to_next()
  {
    while (*this != end_ && !filter_.matches(***this))
      ++(*this);
  }
};

/// Convenience declaration of a unique_ptr<symtab>
class symtab;
using symtab_ptr = std::unique_ptr<symtab>;

/// symtab is the actual data container of the symtab_reader implementation.
///
/// The symtab is instantiated either via an Elf handle (from binary) or from a
/// set of existing symbol maps (usually when instantiated from XML). It will
/// then discover the symtab, possibly the ksymtab (for Linux Kernel binaries)
/// and setup the data containers and lookup maps for later perusal.
///
/// The symtab is supposed to be used in a const context as all information is
/// already computed at construction time. Symbols are stored sorted to allow
/// deterministic reading of the entries.
///
/// An example use of the symtab class is
///
/// const auto symtab    = symtab::load(elf_handle, env);
/// symtab_filter filter = symtab->make_filter();
/// filter.set_public_symbols();
/// filter.set_functions();
///
/// for (const auto& symbol : filtered_symtab(*symtab, filter))
///   {
///     std::cout << symbol->get_name() << "\n";
///   }
///
/// This uses the filtered_symtab proxy object to capture the filter.
class symtab
{
public:
  using symbol_predicate = std::function<bool(const elf_symbol_sptr&)>;

  /// Indicate whether any (kernel) symbols have been seen at construction.
  ///
  /// @return true if there are symbols detected earlier.
  bool
  has_symbols() const
  {return is_kernel_binary_ ? has_ksymtab_entries_ : !symbols_.empty();}

  symtab_filter
  make_filter() const;

  /// The (only) iterator type we offer is a const_iterator implemented by the
  /// symtab_iterator.
  using const_iterator = symtab_iterator;

  /// Obtain an iterator to the beginning of the symtab according to the filter
  /// criteria. Whenever this iterator advances, it skips elements that do not
  /// match the filter criteria.
  ///
  /// @param filter the symtab_filter to match symbols against
  ///
  /// @return a filtering const_iterator of the underlying type
  const_iterator
  begin(const symtab_filter& filter) const
  {return symtab_iterator(symbols_.begin(), symbols_.end(), filter);}

  /// Obtain an iterator to the end of the symtab.
  ///
  /// @return an end iterator
  const_iterator
  end() const
  {return symtab_iterator(symbols_.end(), symbols_.end());}

  const elf_symbols&
  lookup_symbol(const std::string& name) const;

  const elf_symbol_sptr&
  lookup_symbol(GElf_Addr symbol_addr) const;

  const elf_symbol_sptr
  lookup_undefined_function_symbol(const std::string& name);

  const elf_symbol_sptr
  lookup_undefined_variable_symbol(const std::string& name);

  elf_symbol_sptr
  function_symbol_is_exported(const string&);

  elf_symbol_sptr
  function_symbol_is_exported(const GElf_Addr symbol_address);

  elf_symbol_sptr
  variable_symbol_is_exported(const string&);

  elf_symbol_sptr
  variable_symbol_is_exported(const GElf_Addr symbol_address);

  elf_symbol_sptr
  function_symbol_is_undefined(const string&);

  elf_symbol_sptr
  variable_symbol_is_undefined(const string&);

  static symtab_ptr
  load(Elf*		elf_handle,
       const ir::environment& env,
       symbol_predicate is_suppressed = NULL);

  static symtab_ptr
  load(string_elf_symbols_map_sptr function_symbol_map,
       string_elf_symbols_map_sptr variables_symbol_map);

  void
  update_main_symbol(GElf_Addr addr, const std::string& name);

private:
  /// Default constructor. Private to enforce creation by factory methods.
  symtab();

  /// The vector of symbols we discovered.
  elf_symbols symbols_;

  // A map of undefined function symbols to speedup the function
  // lookup_undefined_function_symbol
  unordered_map<string, elf_symbol_sptr> undefined_fn_symbols_;

  // Similarly, a map of undefined variable symbols to speedup the
  // function lookup_undefined_variable_symbol
  unordered_map<string, elf_symbol_sptr> undefined_var_symbols_;

  /// Whether this is a Linux Kernel binary
  bool is_kernel_binary_;

  /// Whether this kernel_binary has ksymtab entries
  ///
  /// A kernel module might not have a ksymtab if it does not export any
  /// symbols. In order to quickly decide whether the symbol table is empty, we
  /// remember whether we ever saw ksymtab entries.
  bool has_ksymtab_entries_;

  /// Lookup map name->symbol(s)
  using name_symbol_map_type =
      std::unordered_map<std::string, std::vector<elf_symbol_sptr>>;
  name_symbol_map_type name_symbol_map_;

  /// Lookup map addr->symbol
  using addr_symbol_map_type = std::unordered_map<GElf_Addr, elf_symbol_sptr>;
  addr_symbol_map_type addr_symbol_map_;

  /// Lookup map function entry address -> symbol
  addr_symbol_map_type entry_addr_symbol_map_;

  /// Set of undefined function symbol names
  std::unordered_set<std::string> undefined_function_linkage_names_;

  /// of undefined variable function symbol names
  std::unordered_set<std::string> undefined_variable_linkage_names_;

  bool cached_undefined_symbol_names_;

  bool
  load_(Elf* elf_handle,
	const ir::environment& env,
	symbol_predicate is_suppressed);

  bool
  load_(string_elf_symbols_map_sptr function_symbol_map,
       string_elf_symbols_map_sptr variables_symbol_map);

  GElf_Addr
  setup_symbol_lookup_tables(Elf* elf_handle,
			     GElf_Sym* elf_symbol,
			     const elf_symbol_sptr& symbol_sptr);

  void
  update_function_entry_address_symbol_map(Elf*	     elf_handle,
					   GElf_Sym* native_symbol,
					   const elf_symbol_sptr& symbol_sptr);

  void
  add_alternative_address_lookups(Elf* elf_handle);

  void
  collect_undefined_fns_and_vars_linkage_names();
};

/// Helper class to allow range-for loops on symtabs for C++11 and later code.
/// It serves as a proxy for the symtab iterator and provides a begin() method
/// without arguments, as required for range-for loops (and possibly other
/// iterator based transformations).
///
/// Example usage:
///
///   for (const auto& symbol : filtered_symtab(tab, filter))
///     {
///       std::cout << symbol->get_name() << "\n";
///     }
///
class filtered_symtab
{
  const symtab&	      tab_;
  const symtab_filter filter_;

public:
  /// Construct the proxy object keeping references to the underlying symtab
  /// and the filter object.
  filtered_symtab(const symtab& tab, const symtab_filter& filter)
    : tab_(tab), filter_(filter)
  {}

  /// Pass through symtab.begin(), but also pass on the filter.
  symtab::const_iterator
  begin() const
  {return tab_.begin(filter_);}

  /// Pass through symtab.end().
  symtab::const_iterator
  end() const
  {return tab_.end();}
};

} // end namespace symtab_reader
} // end namespace abigail

#endif // __ABG_SYMTAB_READER_H__
