// Copyright (c) 2013 Austin T. Clements. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

#include "internal.h"

DWARFPP_BEGIN_NAMESPACE

static value::type
resolve_type(DW_AT name, DW_FORM form)
{
        switch (form) {
        case DW_FORM::addr:
                return value::type::address;

        case DW_FORM::block:
        case DW_FORM::block1:
        case DW_FORM::block2:
        case DW_FORM::block4:
                // Prior to DWARF 4, exprlocs didn't have their own
                // form and were represented as blocks.
                // XXX Should this be predicated on version?
                switch (name) {
                case DW_AT::location:
                case DW_AT::byte_size:
                case DW_AT::bit_offset:
                case DW_AT::bit_size:
                case DW_AT::string_length:
                case DW_AT::lower_bound:
                case DW_AT::return_addr:
                case DW_AT::bit_stride:
                case DW_AT::upper_bound:
                case DW_AT::count:
                case DW_AT::data_member_location:
                case DW_AT::frame_base:
                case DW_AT::segment:
                case DW_AT::static_link:
                case DW_AT::use_location:
                case DW_AT::vtable_elem_location:
                case DW_AT::allocated:
                case DW_AT::associated:
                case DW_AT::data_location:
                case DW_AT::byte_stride:
                        return value::type::exprloc;
                default:
                        return value::type::block;
                }

        case DW_FORM::data4:
        case DW_FORM::data8:
                // Prior to DWARF 4, section offsets didn't have their
                // own form and were represented as data4 or data8.
                // DWARF 3 clarified that types that accepted both
                // constants and section offsets were to treat data4
                // and data8 as section offsets and other constant
                // forms as constants.
                // XXX Should this be predicated on version?
                switch (name) {
                case DW_AT::location:
                case DW_AT::stmt_list:
                case DW_AT::string_length:
                case DW_AT::return_addr:
                case DW_AT::start_scope:
                case DW_AT::data_member_location:
                case DW_AT::frame_base:
                case DW_AT::macro_info:
                case DW_AT::segment:
                case DW_AT::static_link:
                case DW_AT::use_location:
                case DW_AT::vtable_elem_location:
                case DW_AT::ranges:
                        goto sec_offset;
                default:
                        // Fall through
                        break;
                }
        case DW_FORM::data1:
        case DW_FORM::data2:
                return value::type::constant;
        case DW_FORM::udata:
                return value::type::uconstant;
        case DW_FORM::sdata:
                return value::type::sconstant;

        case DW_FORM::exprloc:
                return value::type::exprloc;

        case DW_FORM::flag:
        case DW_FORM::flag_present:
                return value::type::flag;

        case DW_FORM::ref1:
        case DW_FORM::ref2:
        case DW_FORM::ref4:
        case DW_FORM::ref8:
        case DW_FORM::ref_addr:
        case DW_FORM::ref_sig8:
        case DW_FORM::ref_udata:
                return value::type::reference;

        case DW_FORM::string:
        case DW_FORM::strp:
                return value::type::string;

        case DW_FORM::indirect:
                // There's nothing meaningful we can do
                return value::type::invalid;

        case DW_FORM::sec_offset:
        sec_offset:
                // The type of this form depends on the attribute
                switch (name) {
                case DW_AT::stmt_list:
                        return value::type::line;

                case DW_AT::location:
                case DW_AT::string_length:
                case DW_AT::return_addr:
                case DW_AT::data_member_location:
                case DW_AT::frame_base:
                case DW_AT::segment:
                case DW_AT::static_link:
                case DW_AT::use_location:
                case DW_AT::vtable_elem_location:
                        return value::type::loclist;

                case DW_AT::macro_info:
                        return value::type::mac;

                case DW_AT::start_scope:
                case DW_AT::ranges:
                        return value::type::rangelist;

                case DW_AT::lo_user...DW_AT::hi_user:
                        //HACK: ignore vendor extensions
                        return value::type::invalid;

                default:
                        throw format_error("DW_FORM_sec_offset not expected for attribute " +
                                           to_string(name));
                }
        }
        throw format_error("unknown attribute form " + to_string(form));
}

attribute_spec::attribute_spec(DW_AT name, DW_FORM form)
        : name(name), form(form), type(resolve_type(name, form))
{
}

bool
abbrev_entry::read(cursor *cur)
{
        attributes.clear();

        // Section 7.5.3
        code = cur->uleb128();
        if (!code)
                return false;

        tag = (DW_TAG)cur->uleb128();
        children = cur->fixed<DW_CHILDREN>() == DW_CHILDREN::yes;
        while (1) {
                DW_AT name = (DW_AT)cur->uleb128();
                DW_FORM form = (DW_FORM)cur->uleb128();
                if (name == (DW_AT)0 && form == (DW_FORM)0)
                        break;
                attributes.push_back(attribute_spec(name, form));
        }
        attributes.shrink_to_fit();
        return true;
}

DWARFPP_END_NAMESPACE
