// Copyright 2006 Google Inc. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// This is a client for the dwarf2reader to extract function and line
// information from the debug info.

#include <assert.h>

#include <map>
#include <queue>
#include <vector>

#include "common/mac/dwarf/functioninfo.h"

#include "common/mac/dwarf/bytereader.h"


namespace dwarf2reader {

// Given an offset value, its form, and the base offset of the
// compilation unit containing this value, return an absolute offset
// within the .debug_info section.
uint64 GetAbsoluteOffset(uint64 offset,
                         enum DwarfForm form,
                         uint64 compilation_unit_base) {
  switch (form) {
    case DW_FORM_ref1:
    case DW_FORM_ref2:
    case DW_FORM_ref4:
    case DW_FORM_ref8:
    case DW_FORM_ref_udata:
      return offset + compilation_unit_base;
    case DW_FORM_ref_addr:
    default:
      return offset;
  }
}

CULineInfoHandler::CULineInfoHandler(vector<SourceFileInfo>* files,
                                     vector<string>* dirs,
                                     LineMap* linemap):linemap_(linemap),
                                                       files_(files),
                                                       dirs_(dirs) {
  // The dirs and files are 1 indexed, so just make sure we put
  // nothing in the 0 vector.
  assert(dirs->size() == 0);
  assert(files->size() == 0);
  dirs->push_back("");
  SourceFileInfo s;
  s.name = "";
  s.lowpc = ULLONG_MAX;
  files->push_back(s);
}

void CULineInfoHandler::DefineDir(const string& name, uint32 dir_num) {
  // These should never come out of order, actually
  assert(dir_num == dirs_->size());
  dirs_->push_back(name);
}

void CULineInfoHandler::DefineFile(const string& name,
                                   int32 file_num, uint32 dir_num,
                                   uint64 mod_time, uint64 length) {
  assert(dir_num >= 0);
  assert(dir_num < dirs_->size());

  // These should never come out of order, actually.
  if (file_num == (int32)files_->size() || file_num == -1) {
    string dir = dirs_->at(dir_num);

    SourceFileInfo s;
    s.lowpc = ULLONG_MAX;

    if (dir == "") {
      s.name = name;
    } else {
      s.name = dir + "/" + name;
    }

    files_->push_back(s);
  } else {
    fprintf(stderr, "error in DefineFile");
  }
}

void CULineInfoHandler::AddLine(uint64 address, uint32 file_num,
                                uint32 line_num, uint32 column_num) {
  if (file_num < files_->size()) {
    linemap_->insert(make_pair(address, make_pair(files_->at(file_num).name.c_str(),
                                                  line_num)));

    if(address < files_->at(file_num).lowpc) {
      files_->at(file_num).lowpc = address;
    }
  } else {
    fprintf(stderr,"error in AddLine");
  }
}

bool CUFunctionInfoHandler::StartCompilationUnit(uint64 offset,
                                                 uint8 address_size,
                                                 uint8 offset_size,
                                                 uint64 cu_length,
                                                 uint8 dwarf_version) {
  current_compilation_unit_offset_ = offset;
  return true;
}


// For function info, we only care about subprograms and inlined
// subroutines. For line info, the DW_AT_stmt_list lives in the
// compile unit tag.

bool CUFunctionInfoHandler::StartDIE(uint64 offset, enum DwarfTag tag,
                                     const AttributeList& attrs) {
  switch (tag) {
    case DW_TAG_subprogram:
    case DW_TAG_inlined_subroutine: {
      current_function_info_ = new FunctionInfo;
      current_function_info_->lowpc = current_function_info_->highpc = 0;
      current_function_info_->name = "";
      current_function_info_->line = 0;
      current_function_info_->file = "";
      offset_to_funcinfo_->insert(make_pair(offset, current_function_info_));
    };
      // FALLTHROUGH
    case DW_TAG_compile_unit:
      return true;
    default:
      return false;
  }
  return false;
}

// Only care about the name attribute for functions

void CUFunctionInfoHandler::ProcessAttributeString(uint64 offset,
                                                   enum DwarfAttribute attr,
                                                   enum DwarfForm form,
                                                   const string &data) {
  if (current_function_info_) {
    if (attr == DW_AT_name)
      current_function_info_->name = data;
    else if(attr == DW_AT_MIPS_linkage_name)
      current_function_info_->mangled_name = data;
  }
}

void CUFunctionInfoHandler::ProcessAttributeUnsigned(uint64 offset,
                                                     enum DwarfAttribute attr,
                                                     enum DwarfForm form,
                                                     uint64 data) {
  if (attr == DW_AT_stmt_list) {
    SectionMap::const_iterator iter = sections_.find("__debug_line");
    assert(iter != sections_.end());

    // this should be a scoped_ptr but we dont' use boost :-(
    auto_ptr<LineInfo> lireader(new LineInfo(iter->second.first + data,
                                               iter->second.second  - data,
                                               reader_, linehandler_));
    lireader->Start();
  } else if (current_function_info_) {
    switch (attr) {
      case DW_AT_low_pc:
        current_function_info_->lowpc = data;
        break;
      case DW_AT_high_pc:
        current_function_info_->highpc = data;
        break;
      case DW_AT_decl_line:
        current_function_info_->line = data;
        break;
      case DW_AT_decl_file:
        current_function_info_->file = files_->at(data).name;
        break;
      case DW_AT_specification: {
        // Some functions have a "specification" attribute
        // which means they were defined elsewhere. The name
        // attribute is not repeated, and must be taken from
        // the specification DIE. Here we'll assume that
        // any DIE referenced in this manner will already have
        // been seen, but that's not really required by the spec.
        uint64 abs_offset = GetAbsoluteOffset(data, form, current_compilation_unit_offset_);
        FunctionMap::iterator iter = offset_to_funcinfo_->find(abs_offset);
        if (iter != offset_to_funcinfo_->end()) {
          current_function_info_->name = iter->second->name;
          current_function_info_->mangled_name = iter->second->mangled_name;
        } else {
          // If you hit this, this code probably needs to be rewritten.
          fprintf(stderr, "Error: DW_AT_specification was seen before the referenced DIE! (Looking for DIE at offset %08llx, in DIE at offset %08llx)\n", abs_offset, offset);
        }
        break;
      }
      default:
        break;
    }
  }
}

void CUFunctionInfoHandler::EndDIE(uint64 offset) {
  if (current_function_info_ && current_function_info_->lowpc)
    address_to_funcinfo_->insert(make_pair(current_function_info_->lowpc,
                                           current_function_info_));
}

}  // namespace dwarf2reader
