//===----------------------------------------------------------------------===//
//
// 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 "UnusedReturnValueCheck.h"
#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/OperatorKinds.h"

using namespace clang::ast_matchers;
using namespace clang::ast_matchers::internal;

namespace clang::tidy::bugprone {

namespace {

// Matches functions that are instantiated from a class template member function
// matching InnerMatcher. Functions not instantiated from a class template
// member function are matched directly with InnerMatcher.
AST_MATCHER_P(FunctionDecl, isInstantiatedFrom, Matcher<FunctionDecl>,
              InnerMatcher) {
  const FunctionDecl *InstantiatedFrom =
      Node.getInstantiatedFromMemberFunction();
  return InnerMatcher.matches(InstantiatedFrom ? *InstantiatedFrom : Node,
                              Finder, Builder);
}

constexpr std::initializer_list<OverloadedOperatorKind>
    AssignmentOverloadedOperatorKinds = {
        OO_Equal,      OO_PlusEqual,     OO_MinusEqual,          OO_StarEqual,
        OO_SlashEqual, OO_PercentEqual,  OO_CaretEqual,          OO_AmpEqual,
        OO_PipeEqual,  OO_LessLessEqual, OO_GreaterGreaterEqual, OO_PlusPlus,
        OO_MinusMinus};

AST_MATCHER(FunctionDecl, isAssignmentOverloadedOperator) {
  return llvm::is_contained(AssignmentOverloadedOperatorKinds,
                            Node.getOverloadedOperator());
}
} // namespace

UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name,
                                               ClangTidyContext *Context)
    : ClangTidyCheck(Name, Context),
      CheckedFunctions(utils::options::parseStringList(
          Options.get("CheckedFunctions", "^::std::async$;"
                                          "^::std::launder$;"
                                          "^::std::remove$;"
                                          "^::std::remove_if$;"
                                          "^::std::unique$;"
                                          "^::std::unique_ptr::release$;"
                                          "^::std::basic_string::empty$;"
                                          "^::std::vector::empty$;"
                                          "^::std::back_inserter$;"
                                          "^::std::distance$;"
                                          "^::std::find$;"
                                          "^::std::find_if$;"
                                          "^::std::inserter$;"
                                          "^::std::lower_bound$;"
                                          "^::std::make_pair$;"
                                          "^::std::map::count$;"
                                          "^::std::map::find$;"
                                          "^::std::map::lower_bound$;"
                                          "^::std::multimap::equal_range$;"
                                          "^::std::multimap::upper_bound$;"
                                          "^::std::set::count$;"
                                          "^::std::set::find$;"
                                          "^::std::setfill$;"
                                          "^::std::setprecision$;"
                                          "^::std::setw$;"
                                          "^::std::upper_bound$;"
                                          "^::std::vector::at$;"
                                          // C standard library
                                          "^::bsearch$;"
                                          "^::ferror$;"
                                          "^::feof$;"
                                          "^::isalnum$;"
                                          "^::isalpha$;"
                                          "^::isblank$;"
                                          "^::iscntrl$;"
                                          "^::isdigit$;"
                                          "^::isgraph$;"
                                          "^::islower$;"
                                          "^::isprint$;"
                                          "^::ispunct$;"
                                          "^::isspace$;"
                                          "^::isupper$;"
                                          "^::iswalnum$;"
                                          "^::iswprint$;"
                                          "^::iswspace$;"
                                          "^::isxdigit$;"
                                          "^::memchr$;"
                                          "^::memcmp$;"
                                          "^::strcmp$;"
                                          "^::strcoll$;"
                                          "^::strncmp$;"
                                          "^::strpbrk$;"
                                          "^::strrchr$;"
                                          "^::strspn$;"
                                          "^::strstr$;"
                                          "^::wcscmp$;"
                                          // POSIX
                                          "^::access$;"
                                          "^::bind$;"
                                          "^::connect$;"
                                          "^::difftime$;"
                                          "^::dlsym$;"
                                          "^::fnmatch$;"
                                          "^::getaddrinfo$;"
                                          "^::getopt$;"
                                          "^::htonl$;"
                                          "^::htons$;"
                                          "^::iconv_open$;"
                                          "^::inet_addr$;"
                                          "^::isascii$;"
                                          "^::isatty$;"
                                          "^::mmap$;"
                                          "^::newlocale$;"
                                          "^::openat$;"
                                          "^::pathconf$;"
                                          "^::pthread_equal$;"
                                          "^::pthread_getspecific$;"
                                          "^::pthread_mutex_trylock$;"
                                          "^::readdir$;"
                                          "^::readlink$;"
                                          "^::recvmsg$;"
                                          "^::regexec$;"
                                          "^::scandir$;"
                                          "^::semget$;"
                                          "^::setjmp$;"
                                          "^::shm_open$;"
                                          "^::shmget$;"
                                          "^::sigismember$;"
                                          "^::strcasecmp$;"
                                          "^::strsignal$;"
                                          "^::ttyname$"))),
      CheckedReturnTypes(utils::options::parseStringList(
          Options.get("CheckedReturnTypes", "^::std::error_code$;"
                                            "^::std::error_condition$;"
                                            "^::std::errc$;"
                                            "^::std::expected$;"
                                            "^::boost::system::error_code$"))),
      AllowCastToVoid(Options.get("AllowCastToVoid", false)) {}

UnusedReturnValueCheck::UnusedReturnValueCheck(
    llvm::StringRef Name, ClangTidyContext *Context,
    std::vector<StringRef> CheckedFunctions)
    : UnusedReturnValueCheck(Name, Context, std::move(CheckedFunctions), {},
                             false) {}

UnusedReturnValueCheck::UnusedReturnValueCheck(
    llvm::StringRef Name, ClangTidyContext *Context,
    std::vector<StringRef> CheckedFunctions,
    std::vector<StringRef> CheckedReturnTypes, bool AllowCastToVoid)
    : ClangTidyCheck(Name, Context),
      CheckedFunctions(std::move(CheckedFunctions)),
      CheckedReturnTypes(std::move(CheckedReturnTypes)),
      AllowCastToVoid(AllowCastToVoid) {}

void UnusedReturnValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  Options.store(Opts, "CheckedFunctions",
                utils::options::serializeStringList(CheckedFunctions));
  Options.store(Opts, "CheckedReturnTypes",
                utils::options::serializeStringList(CheckedReturnTypes));
  Options.store(Opts, "AllowCastToVoid", AllowCastToVoid);
}

void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) {
  auto MatchedDirectCallExpr = expr(
      callExpr(callee(functionDecl(
                   // Don't match copy or move assignment operator.
                   unless(isAssignmentOverloadedOperator()),
                   // Don't match void overloads of checked functions.
                   unless(returns(voidType())),
                   anyOf(isInstantiatedFrom(matchers::matchesAnyListedRegexName(
                             CheckedFunctions)),
                         returns(hasCanonicalType(hasDeclaration(
                             namedDecl(matchers::matchesAnyListedRegexName(
                                 CheckedReturnTypes)))))))))
          .bind("match"));

  auto CheckCastToVoid =
      AllowCastToVoid ? castExpr(unless(hasCastKind(CK_ToVoid))) : castExpr();
  auto MatchedCallExpr = expr(
      anyOf(MatchedDirectCallExpr,
            explicitCastExpr(unless(cxxFunctionalCastExpr()), CheckCastToVoid,
                             hasSourceExpression(MatchedDirectCallExpr))));

  auto UnusedInCompoundStmt =
      compoundStmt(forEach(MatchedCallExpr),
                   // The checker can't currently differentiate between the
                   // return statement and other statements inside GNU statement
                   // expressions, so disable the checker inside them to avoid
                   // false positives.
                   unless(hasParent(stmtExpr())));
  auto UnusedInIfStmt =
      ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr)));
  auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr));
  auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr));
  auto UnusedInForStmt =
      forStmt(eachOf(hasLoopInit(MatchedCallExpr),
                     hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr)));
  auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr));
  auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr));

  Finder->addMatcher(
      stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt,
                 UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt,
                 UnusedInCaseStmt)),
      this);
}

void UnusedReturnValueCheck::check(const MatchFinder::MatchResult &Result) {
  if (const auto *Matched = Result.Nodes.getNodeAs<CallExpr>("match")) {
    diag(Matched->getBeginLoc(),
         "the value returned by this function should not be disregarded; "
         "neglecting it may lead to errors")
        << Matched->getSourceRange();

    if (!AllowCastToVoid)
      return;

    diag(Matched->getBeginLoc(),
         "cast the expression to void to silence this warning",
         DiagnosticIDs::Note);
  }
}

} // namespace clang::tidy::bugprone
