//===-- RichManglingContext.cpp ---------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "lldb/Core/RichManglingContext.h"

#include "lldb/Utility/Log.h"
#include "lldb/Utility/Logging.h"

#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h"

#include "llvm/ADT/StringRef.h"

using namespace lldb;
using namespace lldb_private;

// RichManglingContext
void RichManglingContext::ResetProvider(InfoProvider new_provider) {
  // If we want to support parsers for other languages some day, we need a
  // switch here to delete the correct parser type.
  if (m_cxx_method_parser.hasValue()) {
    assert(m_provider == PluginCxxLanguage);
    delete get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser);
    m_cxx_method_parser.reset();
  }

  assert(new_provider != None && "Only reset to a valid provider");
  m_provider = new_provider;
}

bool RichManglingContext::FromItaniumName(ConstString mangled) {
  bool err = m_ipd.partialDemangle(mangled.GetCString());
  if (!err) {
    ResetProvider(ItaniumPartialDemangler);
  }

  if (Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DEMANGLE)) {
    if (!err) {
      ParseFullName();
      LLDB_LOG(log, "demangled itanium: {0} -> \"{1}\"", mangled, m_ipd_buf);
    } else {
      LLDB_LOG(log, "demangled itanium: {0} -> error: failed to demangle",
               mangled);
    }
  }

  return !err; // true == success
}

bool RichManglingContext::FromCxxMethodName(ConstString demangled) {
  ResetProvider(PluginCxxLanguage);
  m_cxx_method_parser = new CPlusPlusLanguage::MethodName(demangled);
  return true;
}

bool RichManglingContext::IsCtorOrDtor() const {
  assert(m_provider != None && "Initialize a provider first");
  switch (m_provider) {
  case ItaniumPartialDemangler:
    return m_ipd.isCtorOrDtor();
  case PluginCxxLanguage: {
    // We can only check for destructors here.
    auto base_name =
        get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser)->GetBasename();
    return base_name.startswith("~");
  }
  case None:
    return false;
  }
  llvm_unreachable("Fully covered switch above!");
}

bool RichManglingContext::IsFunction() const {
  assert(m_provider != None && "Initialize a provider first");
  switch (m_provider) {
  case ItaniumPartialDemangler:
    return m_ipd.isFunction();
  case PluginCxxLanguage:
    return get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser)->IsValid();
  case None:
    return false;
  }
  llvm_unreachable("Fully covered switch above!");
}

void RichManglingContext::processIPDStrResult(char *ipd_res, size_t res_size) {
  // Error case: Clear the buffer.
  if (LLVM_UNLIKELY(ipd_res == nullptr)) {
    assert(res_size == m_ipd_buf_size &&
           "Failed IPD queries keep the original size in the N parameter");

    m_ipd_buf[0] = '\0';
    m_buffer = llvm::StringRef(m_ipd_buf, 0);
    return;
  }

  // IPD's res_size includes null terminator.
  assert(ipd_res[res_size - 1] == '\0' &&
         "IPD returns null-terminated strings and we rely on that");

  // Update buffer/size on realloc.
  if (LLVM_UNLIKELY(ipd_res != m_ipd_buf || res_size > m_ipd_buf_size)) {
    m_ipd_buf = ipd_res;       // std::realloc freed or reused the old buffer.
    m_ipd_buf_size = res_size; // May actually be bigger, but we can't know.

    if (Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DEMANGLE))
      LLDB_LOG(log, "ItaniumPartialDemangler Realloc: new buffer size is {0}",
               m_ipd_buf_size);
  }

  // 99% case: Just remember the string length.
  m_buffer = llvm::StringRef(m_ipd_buf, res_size - 1);
}

void RichManglingContext::ParseFunctionBaseName() {
  assert(m_provider != None && "Initialize a provider first");
  switch (m_provider) {
  case ItaniumPartialDemangler: {
    auto n = m_ipd_buf_size;
    auto buf = m_ipd.getFunctionBaseName(m_ipd_buf, &n);
    processIPDStrResult(buf, n);
    return;
  }
  case PluginCxxLanguage:
    m_buffer =
        get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser)->GetBasename();
    return;
  case None:
    return;
  }
}

void RichManglingContext::ParseFunctionDeclContextName() {
  assert(m_provider != None && "Initialize a provider first");
  switch (m_provider) {
  case ItaniumPartialDemangler: {
    auto n = m_ipd_buf_size;
    auto buf = m_ipd.getFunctionDeclContextName(m_ipd_buf, &n);
    processIPDStrResult(buf, n);
    return;
  }
  case PluginCxxLanguage:
    m_buffer =
        get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser)->GetContext();
    return;
  case None:
    return;
  }
}

void RichManglingContext::ParseFullName() {
  assert(m_provider != None && "Initialize a provider first");
  switch (m_provider) {
  case ItaniumPartialDemangler: {
    auto n = m_ipd_buf_size;
    auto buf = m_ipd.finishDemangle(m_ipd_buf, &n);
    processIPDStrResult(buf, n);
    return;
  }
  case PluginCxxLanguage:
    m_buffer = get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser)
                   ->GetFullName()
                   .GetStringRef();
    return;
  case None:
    return;
  }
}
