//===----------------------- PartialDemangleTest.cpp ----------------------===//
//
// 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 <cstdlib>
#include "llvm/Demangle/Demangle.h"
#include "gtest/gtest.h"

struct ChoppedName {
  const char *Mangled;
  const char *ContextName, *BaseName, *ReturnType, *Params;
};

static ChoppedName NamesToTest[] = {
    {"_Z1fv", "", "f", "", "()"},
    {"_ZN1a1b1cIiiiEEvm", "a::b", "c", "void", "(unsigned long)"},
    {"_ZZ5OuterIiEivEN5Inner12inner_memberEv", "int Outer<int>()::Inner",
     "inner_member", "", "()"},
    {"_Z1fIiEPFvvEv", "", "f", "void (*)()", "()"},
    {"_ZN1S1fIiEEvv", "S", "f", "void", "()"},

    // Call operator for a lambda in f().
    {"_ZZ1fvENK3$_0clEi", "f()::$_0", "operator()", "", "(int)"},

    // A call operator for a lambda in a lambda in f().
    {"_ZZZ1fvENK3$_0clEvENKUlvE_clEv",
     "f()::$_0::operator()() const::'lambda'()", "operator()", "", "()"},

    {"_ZZN1S1fEiiEd0_NKUlvE_clEv", "S::f(int, int)::'lambda'()", "operator()",
     "", "()"},

    {"_ZN1Scv7MuncherIJDpPT_EEIJFivEA_iEEEv", "S",
     "operator Muncher<int (*)(), int (*) []>", "", "()"},

    // Attributes.
    {"_ZN5test4IdE1fEUa9enable_ifIXeqfL0p_Li1EEXeqfL0p0_Li2EEEi",
     "test4<double>", "f", "", "(int)"},
    {"_ZN1SC2B8ctor_tagEv", "S", "S", "", "()"},
    {"_ZN1S1fB4MERPIiEEvv", "S", "f", "void", "()"},

    {"_ZNSsC1EmcRKSaIcE",
     "std::basic_string<char, std::char_traits<char>, std::allocator<char>>",
     "basic_string", "", "(unsigned long, char, std::allocator<char> const&)"},
    {"_ZNSsixEm", "std::string", "operator[]", "", "(unsigned long)"},
    {"_ZSt17__throw_bad_allocv", "std", "__throw_bad_alloc", "", "()"},

    {"_ZN1AI1BEC2Ev", "A<B>", "A", "", "()"},
    {"_ZN1AI1BED2Ev", "A<B>", "~A", "", "()"},
    {"_ZN1AI1BECI24BaseEi", "A<B>", "A", "", "(int)"},
    {"_ZNKR1AI1BE1fIiEEiv", "A<B>", "f", "int", "()"},

    {"_ZN1SIJicfEE3mfnIJjcdEEEvicfDpT_", "S<int, char, float>", "mfn", "void",
     "(int, char, float, unsigned int, char, double)"},

    {"_Z1fDAs", "", "f", "", "(short _Accum)"},
    {"_Z1fDAt", "", "f", "", "(unsigned short _Accum)"},
    {"_Z1fDAi", "", "f", "", "(_Accum)"},
    {"_Z1fDAj", "", "f", "", "(unsigned _Accum)"},
    {"_Z1fDAl", "", "f", "", "(long _Accum)"},
    {"_Z1fDAm", "", "f", "", "(unsigned long _Accum)"},
    {"_Z1fDRs", "", "f", "", "(short _Fract)"},
    {"_Z1fDRt", "", "f", "", "(unsigned short _Fract)"},
    {"_Z1fDRi", "", "f", "", "(_Fract)"},
    {"_Z1fDRj", "", "f", "", "(unsigned _Fract)"},
    {"_Z1fDRl", "", "f", "", "(long _Fract)"},
    {"_Z1fDRm", "", "f", "", "(unsigned long _Fract)"},
    {"_Z1fDSDAs", "", "f", "", "(_Sat short _Accum)"},
    {"_Z1fDSDAt", "", "f", "", "(_Sat unsigned short _Accum)"},
    {"_Z1fDSDAi", "", "f", "", "(_Sat _Accum)"},
    {"_Z1fDSDAj", "", "f", "", "(_Sat unsigned _Accum)"},
    {"_Z1fDSDAl", "", "f", "", "(_Sat long _Accum)"},
    {"_Z1fDSDAm", "", "f", "", "(_Sat unsigned long _Accum)"},
    {"_Z1fDSDRs", "", "f", "", "(_Sat short _Fract)"},
    {"_Z1fDSDRt", "", "f", "", "(_Sat unsigned short _Fract)"},
    {"_Z1fDSDRi", "", "f", "", "(_Sat _Fract)"},
    {"_Z1fDSDRj", "", "f", "", "(_Sat unsigned _Fract)"},
    {"_Z1fDSDRl", "", "f", "", "(_Sat long _Fract)"},
    {"_Z1fDSDRm", "", "f", "", "(_Sat unsigned long _Fract)"},
};

TEST(PartialDemanglerTest, TestNameChopping) {
  size_t Size = 1;
  char *Buf = static_cast<char *>(std::malloc(Size));

  llvm::ItaniumPartialDemangler D;

  for (ChoppedName &N : NamesToTest) {
    EXPECT_FALSE(D.partialDemangle(N.Mangled));
    EXPECT_TRUE(D.isFunction());
    EXPECT_FALSE(D.isData());
    EXPECT_FALSE(D.isSpecialName());

    Buf = D.getFunctionDeclContextName(Buf, &Size);
    EXPECT_STREQ(Buf, N.ContextName);

    Buf = D.getFunctionBaseName(Buf, &Size);
    EXPECT_STREQ(Buf, N.BaseName);

    Buf = D.getFunctionReturnType(Buf, &Size);
    EXPECT_STREQ(Buf, N.ReturnType);

    Buf = D.getFunctionParameters(Buf, &Size);
    EXPECT_STREQ(Buf, N.Params);
  }

  std::free(Buf);
}

TEST(PartialDemanglerTest, TestNameMeta) {
  llvm::ItaniumPartialDemangler Demangler;

  EXPECT_FALSE(Demangler.partialDemangle("_ZNK1f1gEv"));
  EXPECT_TRUE(Demangler.isFunction());
  EXPECT_TRUE(Demangler.hasFunctionQualifiers());
  EXPECT_FALSE(Demangler.isSpecialName());
  EXPECT_FALSE(Demangler.isData());

  EXPECT_FALSE(Demangler.partialDemangle("_Z1fv"));
  EXPECT_FALSE(Demangler.hasFunctionQualifiers());

  EXPECT_FALSE(Demangler.partialDemangle("_ZTV1S"));
  EXPECT_TRUE(Demangler.isSpecialName());
  EXPECT_FALSE(Demangler.isData());
  EXPECT_FALSE(Demangler.isFunction());

  EXPECT_FALSE(Demangler.partialDemangle("_ZN1aDC1a1b1cEE"));
  EXPECT_FALSE(Demangler.isFunction());
  EXPECT_FALSE(Demangler.isSpecialName());
  EXPECT_TRUE(Demangler.isData());
}

TEST(PartialDemanglerTest, TestCtorOrDtor) {
  static const char *Pos[] = {
      "_ZN1AC1Ev",        // A::A()
      "_ZN1AC1IiEET_",    // A::A<int>(int)
      "_ZN1AD2Ev",        // A::~A()
      "_ZN1BIiEC1IcEET_", // B<int>::B<char>(char)
      "_ZN1AC1B1TEv",     // A::A[abi:T]()
      "_ZNSt1AD2Ev",      // std::A::~A()
      "_ZN2ns1AD1Ev",      // ns::A::~A()
  };
  static const char *Neg[] = {
      "_Z1fv",
      "_ZN1A1gIiEEvT_", // void A::g<int>(int)
  };

  llvm::ItaniumPartialDemangler D;
  for (const char *N : Pos) {
    EXPECT_FALSE(D.partialDemangle(N));
    EXPECT_TRUE(D.isCtorOrDtor());
  }
  for (const char *N : Neg) {
    EXPECT_FALSE(D.partialDemangle(N));
    EXPECT_FALSE(D.isCtorOrDtor());
  }
}

TEST(PartialDemanglerTest, TestMisc) {
  llvm::ItaniumPartialDemangler D1, D2;

  EXPECT_FALSE(D1.partialDemangle("_Z1fv"));
  EXPECT_FALSE(D2.partialDemangle("_Z1g"));
  std::swap(D1, D2);
  EXPECT_FALSE(D1.isFunction());
  EXPECT_TRUE(D2.isFunction());

  EXPECT_TRUE(D1.partialDemangle("Not a mangled name!"));
}

TEST(PartialDemanglerTest, TestPrintCases) {
  llvm::ItaniumPartialDemangler D;

  const size_t OriginalSize = 4;
  char *Buf = static_cast<char *>(std::malloc(OriginalSize));
  const char *OriginalBuf = Buf;

  // Default success case: Result fits into the given buffer.
  // Res points to Buf. N returns string size including null termination.
  {
    EXPECT_FALSE(D.partialDemangle("_ZN1a1bEv"));

    size_t N = OriginalSize;
    char *Res = D.getFunctionDeclContextName(Buf, &N);
    EXPECT_STREQ("a", Res);
    EXPECT_EQ(OriginalBuf, Res);
    EXPECT_EQ(strlen(Res) + 1, N);
  }

  // Realloc success case: Result does not fit into the given buffer.
  // Res points to the new or extended buffer. N returns string size
  // including null termination. Buf was extended or freed.
  {
    EXPECT_FALSE(D.partialDemangle("_ZN1a1b1cIiiiEEvm"));

    size_t N = OriginalSize;
    char *Res = D.finishDemangle(Buf, &N);
    EXPECT_STREQ("void a::b::c<int, int, int>(unsigned long)", Res);
    EXPECT_EQ(strlen(Res) + 1, N);
    Buf = Res;
  }

  // Failure case: a::c is not a function.
  // Res is nullptr. N remains unchanged.
  {
    EXPECT_FALSE(D.partialDemangle("_ZN1a1cE"));

    size_t N = OriginalSize;
    char *Res = D.getFunctionName(Buf, &N);
    EXPECT_EQ(nullptr, Res);
    EXPECT_EQ(OriginalSize, N);
  }

  std::free(Buf);
}
