/* dwarfpp: C++ binding for a useful subset of libdwarf, plus extra goodies.
 * 
 * expr.hpp: simple C++ abstraction of DWARF expressions and location lists.
 *
 * Copyright (c) 2010--17, Stephen Kell. For licensing information, see the
 * LICENSE file in the root of the libdwarfpp tree.
 */

#ifndef DWARFPP_EXPR_HPP_
#define DWARFPP_EXPR_HPP_

#include <vector>
#include <stack>
#include <boost/icl/interval_map.hpp>
#include <strings.h> // for bzero
#include "spec.hpp"
#include "libdwarf.hpp"
#include "libdwarf-handles.hpp"
#include "abstract.hpp"
#include "opt.hpp"

namespace dwarf
{
	using std::vector;
	using std::pair;
	using std::set;
	using std::vector;
	using std::string;
	
	namespace expr
	{
		class evaluator;
		class loclist;
		
		/* We don't support all expressions. */
		class Not_supported
		{
			const string& m_msg;
		public:
			Not_supported(const string& msg) : m_msg(msg) {}
		};
		class regs
		{
		public:
			virtual lib::Dwarf_Signed get(int regnum) = 0;
			virtual void set(int regnum, lib::Dwarf_Signed val) 
			{ throw Not_supported("writing registers"); }
		};
	}
	
	namespace core 
	{ struct Locdesc; struct LocdescList; struct RangesList; struct FrameSection; struct Cie; struct FdeRange; }
	namespace encap
	{
		using namespace dwarf::lib;
		using dwarf::spec::opt;
		
		class rangelist : public vector<lib::Dwarf_Ranges>
		{
		public:
			template <class In> rangelist(In first, In last)
			: vector<lib::Dwarf_Ranges>(first, last) {}
			rangelist() : vector<lib::Dwarf_Ranges>() {}
			
			rangelist(const core::RangesList& rl);
			
			opt<std::pair<Dwarf_Off, long> >
			find_addr(Dwarf_Off file_relative_addr);
		};
		std::ostream& operator<<(std::ostream& s, const rangelist& rl);

		typedef ::dwarf::lib::Dwarf_Loc expr_instr;
		inline bool operator==(const expr_instr& i1, const expr_instr& i2)
		{
			// FIXME: ignore don't-care fields? needs spec knowledge :-(
			return i1.lr_atom == i2.lr_atom
				&& i1.lr_number == i2.lr_number
				&& i1.lr_number2 == i2.lr_number2
				&& i1.lr_offset == i2.lr_offset;
		}

		struct loc_expr : public vector<expr_instr>
		{
			/* We used to have NO_LOCATION here. But we don't need it! Recap: 
			 * In DWARF, hipc == 0 && lopc == 0 means an "end of list entry".
			 * BUT libdwarf abstracts this so that we don't see end-of-list
			 * entries (I *think*). 
			 * THEN it uses hipc==0 and lopc==0 to mean "all vaddrs"
			 * (see libdwarf2.1.pdf sec 2.3.2). 
			 * So we have to interpret it that way. If we want to encode 
			 * "no location", e.g. in with_dynamic_location_die::get_dynamic_location(),
			 * we use an empty loclist. */
			const dwarf::spec::abstract_def& spec;
			Dwarf_Addr hipc;
			Dwarf_Addr lopc;
			loc_expr(spec::abstract_def& spec = spec::dwarf_current) 
			: spec(spec), hipc(0), lopc(0)/*, m_expr(*this)*/ {}
			loc_expr(const lib::Dwarf_Locdesc& desc, const spec::abstract_def& spec = spec::dwarf_current) : 
				vector<expr_instr>(desc.ld_s, desc.ld_s + desc.ld_cents),
				spec(spec), hipc(desc.ld_hipc), lopc(desc.ld_lopc)/*,
				m_expr(*this)*/ {}
			loc_expr(Dwarf_Debug dbg, lib::Dwarf_Ptr instrs, lib::Dwarf_Unsigned len, const spec::abstract_def& spec = spec::dwarf_current);
			loc_expr(const vector<expr_instr>& expr,
				const spec::abstract_def& spec = spec::dwarf_current)
			: vector<expr_instr>(expr),
			  spec(spec), hipc(0), lopc(0)/*, m_expr(*this)*/ {}
			loc_expr(const loc_expr& arg)  // copy constructor
			: vector<expr_instr>(arg.begin(), arg.end()),
			  spec(arg.spec), hipc(arg.hipc), lopc(arg.lopc)/*, 
			  m_expr(*this)*/ {}

			loc_expr piece_for_offset(Dwarf_Off offset) const;
			vector<std::pair<loc_expr, Dwarf_Unsigned> > pieces() const;
			
			// this is languishing here because it's a HACK.. should take the value as argument
			// too, to calculate variable-length encodings correctly
			size_t form_encoded_size(Dwarf_Half form)
			{
				switch(form)
				{
					case DW_FORM_addr: return sizeof (Dwarf_Addr); 
					case DW_FORM_block2: return 2;
					case DW_FORM_block4: return 4;
					case DW_FORM_data2: return 2;
					case DW_FORM_data4: return 4;
					case DW_FORM_data8: return 8;
					case DW_FORM_string: return sizeof (Dwarf_Unsigned);
					case DW_FORM_block: return sizeof (Dwarf_Unsigned);
					case DW_FORM_block1: return 1;
					case DW_FORM_data1: return 1;
					case DW_FORM_flag: return 1;
					case DW_FORM_sdata: return sizeof (Dwarf_Unsigned);
					case DW_FORM_strp: return sizeof (Dwarf_Addr);
					case DW_FORM_udata: return sizeof (Dwarf_Unsigned);
					case DW_FORM_ref_addr: return sizeof (Dwarf_Addr);
					case DW_FORM_ref1: return 1;
					case DW_FORM_ref2: return 2;
					case DW_FORM_ref4: return 4;
					case DW_FORM_ref8: return 8;
					case DW_FORM_ref_udata: return sizeof (Dwarf_Unsigned);
					case DW_FORM_indirect: return sizeof (Dwarf_Addr);
					default: assert(false); return 0;					
				}
			}
			
			template <class In> loc_expr(In first, In last, 
				const spec::abstract_def& spec = spec::dwarf_current) 
			: vector<expr_instr>(first, last),
			  spec(spec), /*m_expr(first, last), */hipc(0), lopc(0) {}
			  
			/* This template parses a location expression out of an array of unsigneds. */
			template<size_t s> 
			loc_expr(Dwarf_Unsigned const (&arr)[s], Dwarf_Addr lopc, Dwarf_Addr hipc,
				const spec::abstract_def& spec = spec::dwarf_current) 
			: spec(spec), hipc(hipc), lopc(lopc)
			{
				initialize_from_opcode_array(&arr[0], &arr[s], lopc, hipc, spec);
			}
			
			template <class In>
			loc_expr(In begin, In end, Dwarf_Addr lopc, Dwarf_Addr hipc,
				const spec::abstract_def& spec = spec::dwarf_current) 
			: spec(spec), hipc(hipc), lopc(lopc)
			{
				initialize_from_opcode_array(begin, end, lopc, hipc, spec);
			}
		private:
			template <class In>
			void initialize_from_opcode_array(In begin, In end,
				Dwarf_Addr lopc, Dwarf_Addr hipc,
				const spec::abstract_def& spec) 
			{
				//size_t s = end - begin;
				auto iter = begin; // &arr[0];
				Dwarf_Unsigned next_offset = 0U;
				while (iter < /* arr + s */ end)
				{
					Dwarf_Loc loc;
					bzero(&loc, sizeof loc);
					loc.lr_offset = next_offset;
					loc.lr_atom = *iter++; // read opcode
					next_offset += 1; // opcodes are one byte
					switch (spec.op_operand_count(loc.lr_atom))
					{
						case 2:
							loc.lr_number = *iter++;
							loc.lr_number2 = *iter++;
							// how many bytes of DWARF binary encoding?
							next_offset += form_encoded_size(
								spec.op_operand_form_list(loc.lr_atom)[0]
							);
							next_offset += form_encoded_size(
								spec.op_operand_form_list(loc.lr_atom)[1]
							);						
							break;
						case 1:
							loc.lr_number = *iter++;
							// how many bytes of DWARF binary encoding?
							next_offset += form_encoded_size(
								spec.op_operand_form_list(loc.lr_atom)[0]
							);
							break;
						case 0:
							break;
						default: assert(false);
					}
					/*m_expr.*/push_back(loc);
				}
			}
		public:
			bool operator==(const loc_expr& e) const 
			{ 
				//expr_instr e1; expr_instr e2;
				return hipc == e.hipc &&
					lopc == e.lopc &&
					//e1 == e2;
					static_cast<const vector<expr_instr>&>(*this)
						== static_cast<const vector<expr_instr>&>(e);
			}
			bool operator!=(const loc_expr& e) const { return !(*this == e); }
			loc_expr& operator=(const loc_expr& e) 
			{ 
				assert(&(this->spec) == &(e.spec)); // references aren't assignable
				*static_cast<vector<expr_instr> *>(this) = *static_cast<const vector<expr_instr> *>(&e);
				this->hipc = e.hipc;
				this->lopc = e.lopc;
				return *this;
			}
			friend std::ostream& operator<<(std::ostream& s, const loc_expr& e);
		};
		
		struct loclist : public vector<loc_expr>
		{
			friend class ::dwarf::expr::evaluator;
			friend class attribute_value;
			static loclist NO_LOCATION;

			loclist() {} // empty loclist
			loclist(const dwarf::expr::loclist& dll);
			/* We can construct a loc_expr from a Loc_Desc. 
			 * So we can construct a loclist from a LocdescList. */
			loclist(const core::LocdescList& ll); 
			// would ideally repeat all vector constructors
			template <class In> loclist(In first, In last) : vector<loc_expr>(first, last) {}
			loclist(const core::Locdesc& l); 
			loclist(const vector<loc_expr>& v) : vector<loc_expr>(v) {}
			loclist(const loc_expr& loc) : vector<loc_expr>(1, loc) {}
			loc_expr loc_for_vaddr(Dwarf_Addr vaddr) const;
			// boost::icl::interval_map<Dwarf_Addr, vector<expr_instr> > as_interval_map() const;
			set< boost::icl::discrete_interval<Dwarf_Addr> > intervals() const
			{ 
				set< boost::icl::discrete_interval<Dwarf_Addr> > working;
				for (auto i_expr = begin(); i_expr != end(); ++i_expr)
				{
					working.insert(boost::icl::discrete_interval<Dwarf_Addr>::right_open(
						i_expr->lopc,
						i_expr->hipc
					));
				}
				return working;
			}
		};
		std::ostream& operator<<(std::ostream& s, const loclist& ll);
		
		/* Instruction sequences in a CIE/FDE. */
		struct frame_instrlist;
		/* We need this extension so that we can define operator<<, since to construct a 
		 * loc_expr will require us to pass the Dwarf_Debug. */
		struct frame_instr : public Dwarf_Frame_Op3
		{
			Dwarf_Debug dbg;
			frame_instr(Dwarf_Debug dbg, const Dwarf_Frame_Op3 arg)
			 : Dwarf_Frame_Op3(arg), dbg(dbg) {}
		};
		std::ostream& operator<<(std::ostream& s, const frame_instr& arg);
		
		struct frame_instrlist : public vector<frame_instr>
		{
			using vector::vector;

			frame_instrlist(const core::Cie& cie, int addrlen, const pair<unsigned char*, unsigned char*>& seq, bool use_host_byte_order = true);
		};
		std::ostream& operator<<(std::ostream& s, const frame_instrlist& arg);
		
		/* Utility function for loclists. */
		loclist absolute_loclist_to_additive_loclist(const loclist& l);
		
		loclist rewrite_loclist_in_terms_of_cfa(
			const loclist& l, 
			const core::FrameSection& fs, 
			dwarf::spec::opt<const loclist&> opt_fbreg // fbreg is special -- loc exprs can refer to it
			);
		Dwarf_Unsigned read_uleb128(unsigned char const **cur, unsigned char const *limit);
		Dwarf_Signed read_sleb128(unsigned char const **cur, unsigned char const *limit);
		uint64_t read_8byte_le(unsigned char const **cur, unsigned char const *limit);
		uint32_t read_4byte_le(unsigned char const **cur, unsigned char const *limit);
		uint16_t read_2byte_le(unsigned char const **cur, unsigned char const *limit);
		uint64_t read_8byte_be(unsigned char const **cur, unsigned char const *limit);
		uint32_t read_4byte_be(unsigned char const **cur, unsigned char const *limit);
		uint16_t read_2byte_be(unsigned char const **cur, unsigned char const *limit);
		uint16_t read_2byte_be(unsigned char const **cur, unsigned char const *limit);
	} // end namespace encap
	
	namespace expr
	{
		using namespace dwarf::lib;
		using dwarf::spec::opt;
		using std::stack;
		using std::ostream;
		class evaluator {
			std::stack<Dwarf_Unsigned> m_stack;
			vector<Dwarf_Loc> expr;
			const ::dwarf::spec::abstract_def& spec;
			regs *p_regs; // optional set of register values, for DW_OP_breg*
			bool tos_is_value; // whether we saw a DW_OP_stack_value hence have calculated a value not an addr
			opt<Dwarf_Signed> frame_base;
			vector<Dwarf_Loc>::iterator i;
			void eval();
		public:
			evaluator(const vector<unsigned char> expr, 
				const ::dwarf::spec::abstract_def& spec) : spec(spec), p_regs(0), tos_is_value(false)
			{
				//i = expr.begin();
				assert(false);
			}
			evaluator(const encap::loclist& loclist,
				Dwarf_Addr vaddr,
				const ::dwarf::spec::abstract_def& spec = spec::DEFAULT_DWARF_SPEC,
				regs *p_regs = 0,
				opt<Dwarf_Signed> frame_base = opt<Dwarf_Signed>(),
				const stack<Dwarf_Unsigned>& initial_stack = stack<Dwarf_Unsigned>());
			
			evaluator(const vector<Dwarf_Loc>& loc_desc,
				const ::dwarf::spec::abstract_def& spec,
				const stack<Dwarf_Unsigned>& initial_stack = stack<Dwarf_Unsigned>())
				: m_stack(initial_stack), spec(spec), p_regs(0), tos_is_value(false)
			{
				expr = loc_desc;
				i = expr.begin();
				eval();
			}
			evaluator(const vector<Dwarf_Loc>& loc_desc,
				const ::dwarf::spec::abstract_def& spec,
				regs& regs,
				Dwarf_Signed frame_base,
				const stack<Dwarf_Unsigned>& initial_stack = stack<Dwarf_Unsigned>()) 
				: m_stack(initial_stack), spec(spec), p_regs(&regs), tos_is_value(false)
			{
				expr = loc_desc;
				i = expr.begin();
				this->frame_base = frame_base;
				eval();
			}

			evaluator(const vector<Dwarf_Loc>& loc_desc,
				const ::dwarf::spec::abstract_def& spec,
				Dwarf_Signed frame_base,
				const stack<Dwarf_Unsigned>& initial_stack = stack<Dwarf_Unsigned>()) 
				: m_stack(initial_stack), spec(spec), p_regs(0), tos_is_value(false)
			{
				//if (av.get_form() != dwarf::encap::attribute_value::LOCLIST) throw "not a DWARF expression";
				//if (av.get_loclist().size() != 1) throw "only support singleton loclists for now";
				//expr = *(av.get_loclist().begin());
				expr = loc_desc;
				i = expr.begin();
				this->frame_base = frame_base;
				eval();
			}
			
			Dwarf_Unsigned tos() const { return m_stack.top(); }
			Dwarf_Unsigned tos(bool may_be_value) const { // FIXME: more complete+orthogonal interface
				if (may_be_value) return m_stack.top();
				if (!tos_is_value && !may_be_value) return m_stack.top();
				throw No_entry();
			}
			bool finished() const { return i == expr.end(); }
			Dwarf_Loc current() const { return *i; }
		};
		Dwarf_Unsigned eval(const encap::loclist& loclist,
			Dwarf_Addr vaddr,
			Dwarf_Signed frame_base,
			opt<regs&> rs,
			const ::dwarf::spec::abstract_def& spec,
			const stack<Dwarf_Unsigned>& initial_stack);
	} // end namespace expr
}

#endif
