//===----------------------------------------------------------------------===//
//
// 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 "MtUnsafeCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

// Initial list was extracted from gcc documentation
static const clang::StringRef GlibcFunctions[] = {
    "::argp_error",
    "::argp_help",
    "::argp_parse",
    "::argp_state_help",
    "::argp_usage",
    "::asctime",
    "::clearenv",
    "::crypt",
    "::ctime",
    "::cuserid",
    "::drand48",
    "::ecvt",
    "::encrypt",
    "::endfsent",
    "::endgrent",
    "::endhostent",
    "::endnetent",
    "::endnetgrent",
    "::endprotoent",
    "::endpwent",
    "::endservent",
    "::endutent",
    "::endutxent",
    "::erand48",
    "::error_at_line",
    "::exit",
    "::fcloseall",
    "::fcvt",
    "::fgetgrent",
    "::fgetpwent",
    "::gammal",
    "::getchar_unlocked",
    "::getdate",
    "::getfsent",
    "::getfsfile",
    "::getfsspec",
    "::getgrent",
    "::getgrent_r",
    "::getgrgid",
    "::getgrnam",
    "::gethostbyaddr",
    "::gethostbyname",
    "::gethostbyname2",
    "::gethostent",
    "::getlogin",
    "::getmntent",
    "::getnetbyaddr",
    "::getnetbyname",
    "::getnetent",
    "::getnetgrent",
    "::getnetgrent_r",
    "::getopt",
    "::getopt_long",
    "::getopt_long_only",
    "::getpass",
    "::getprotobyname",
    "::getprotobynumber",
    "::getprotoent",
    "::getpwent",
    "::getpwent_r",
    "::getpwnam",
    "::getpwuid",
    "::getservbyname",
    "::getservbyport",
    "::getservent",
    "::getutent",
    "::getutent_r",
    "::getutid",
    "::getutid_r",
    "::getutline",
    "::getutline_r",
    "::getutxent",
    "::getutxid",
    "::getutxline",
    "::getwchar_unlocked",
    "::glob",
    "::glob64",
    "::gmtime",
    "::hcreate",
    "::hdestroy",
    "::hsearch",
    "::innetgr",
    "::jrand48",
    "::l64a",
    "::lcong48",
    "::lgammafNx",
    "::localeconv",
    "::localtime",
    "::login",
    "::login_tty",
    "::logout",
    "::logwtmp",
    "::lrand48",
    "::mallinfo",
    "::mallopt",
    "::mblen",
    "::mbrlen",
    "::mbrtowc",
    "::mbsnrtowcs",
    "::mbsrtowcs",
    "::mbtowc",
    "::mcheck",
    "::mprobe",
    "::mrand48",
    "::mtrace",
    "::muntrace",
    "::nrand48",
    "::__ppc_get_timebase_freq",
    "::ptsname",
    "::putchar_unlocked",
    "::putenv",
    "::pututline",
    "::pututxline",
    "::putwchar_unlocked",
    "::qecvt",
    "::qfcvt",
    "::register_printf_function",
    "::seed48",
    "::setenv",
    "::setfsent",
    "::setgrent",
    "::sethostent",
    "::sethostid",
    "::setkey",
    "::setlocale",
    "::setlogmask",
    "::setnetent",
    "::setnetgrent",
    "::setprotoent",
    "::setpwent",
    "::setservent",
    "::setutent",
    "::setutxent",
    "::siginterrupt",
    "::sigpause",
    "::sigprocmask",
    "::sigsuspend",
    "::sleep",
    "::srand48",
    "::strsignal",
    "::strtok",
    "::tcflow",
    "::tcsendbreak",
    "::tmpnam",
    "::ttyname",
    "::unsetenv",
    "::updwtmp",
    "::utmpname",
    "::utmpxname",
    "::valloc",
    "::vlimit",
    "::wcrtomb",
    "::wcsnrtombs",
    "::wcsrtombs",
    "::wctomb",
    "::wordexp",
};

static const clang::StringRef PosixFunctions[] = {
    "::asctime",
    "::basename",
    "::catgets",
    "::crypt",
    "::ctime",
    "::dbm_clearerr",
    "::dbm_close",
    "::dbm_delete",
    "::dbm_error",
    "::dbm_fetch",
    "::dbm_firstkey",
    "::dbm_nextkey",
    "::dbm_open",
    "::dbm_store",
    "::dirname",
    "::dlerror",
    "::drand48",
    "::encrypt",
    "::endgrent",
    "::endpwent",
    "::endutxent",
    "::ftw",
    "::getc_unlocked",
    "::getchar_unlocked",
    "::getdate",
    "::getenv",
    "::getgrent",
    "::getgrgid",
    "::getgrnam",
    "::gethostent",
    "::getlogin",
    "::getnetbyaddr",
    "::getnetbyname",
    "::getnetent",
    "::getopt",
    "::getprotobyname",
    "::getprotobynumber",
    "::getprotoent",
    "::getpwent",
    "::getpwnam",
    "::getpwuid",
    "::getservbyname",
    "::getservbyport",
    "::getservent",
    "::getutxent",
    "::getutxid",
    "::getutxline",
    "::gmtime",
    "::hcreate",
    "::hdestroy",
    "::hsearch",
    "::inet_ntoa",
    "::l64a",
    "::lgamma",
    "::lgammaf",
    "::lgammal",
    "::localeconv",
    "::localtime",
    "::lrand48",
    "::mrand48",
    "::nftw",
    "::nl_langinfo",
    "::ptsname",
    "::putc_unlocked",
    "::putchar_unlocked",
    "::putenv",
    "::pututxline",
    "::rand",
    "::readdir",
    "::setenv",
    "::setgrent",
    "::setkey",
    "::setpwent",
    "::setutxent",
    "::strerror",
    "::strsignal",
    "::strtok",
    "::system",
    "::ttyname",
    "::unsetenv",
    "::wcstombs",
    "::wctomb",
};

namespace clang::tidy {

template <> struct OptionEnumMapping<concurrency::MtUnsafeCheck::FunctionSet> {
  static llvm::ArrayRef<
      std::pair<concurrency::MtUnsafeCheck::FunctionSet, StringRef>>
  getEnumMapping() {
    static constexpr std::pair<concurrency::MtUnsafeCheck::FunctionSet,
                               StringRef>
        Mapping[] = {{concurrency::MtUnsafeCheck::FunctionSet::Posix, "posix"},
                     {concurrency::MtUnsafeCheck::FunctionSet::Glibc, "glibc"},
                     {concurrency::MtUnsafeCheck::FunctionSet::Any, "any"}};
    return {Mapping};
  }
};

namespace concurrency {

static ast_matchers::internal::Matcher<clang::NamedDecl>
hasAnyMtUnsafeNames(MtUnsafeCheck::FunctionSet Libc) {
  switch (Libc) {
  case MtUnsafeCheck::FunctionSet::Posix:
    return hasAnyName(PosixFunctions);
  case MtUnsafeCheck::FunctionSet::Glibc:
    return hasAnyName(GlibcFunctions);
  case MtUnsafeCheck::FunctionSet::Any:
    return anyOf(hasAnyName(PosixFunctions), hasAnyName(GlibcFunctions));
  }
  llvm_unreachable("invalid FunctionSet");
}

MtUnsafeCheck::MtUnsafeCheck(StringRef Name, ClangTidyContext *Context)
    : ClangTidyCheck(Name, Context),
      FuncSet(Options.get("FunctionSet", MtUnsafeCheck::FunctionSet::Any)) {}

void MtUnsafeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  Options.store(Opts, "FunctionSet", FuncSet);
}

void MtUnsafeCheck::registerMatchers(MatchFinder *Finder) {
  Finder->addMatcher(
      callExpr(callee(functionDecl(hasAnyMtUnsafeNames(FuncSet))))
          .bind("mt-unsafe"),
      this);
}

void MtUnsafeCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("mt-unsafe");
  assert(Call && "Unhandled binding in the Matcher");

  diag(Call->getBeginLoc(), "function is not thread safe");
}

} // namespace concurrency
} // namespace clang::tidy
