//===- unittests/StaticAnalyzer/CallDescriptionTest.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 "Reusables.h"

#include "clang/AST/ExprCXX.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
#include <type_traits>

namespace clang {
namespace ento {
namespace {

// A wrapper around CallDescriptionMap<bool> that allows verifying that
// all functions have been found. This is needed because CallDescriptionMap
// isn't supposed to support iteration.
class ResultMap {
  size_t Found, Total;
  CallDescriptionMap<bool> Impl;

public:
  ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data)
      : Found(0),
        Total(std::count_if(Data.begin(), Data.end(),
                            [](const std::pair<CallDescription, bool> &Pair) {
                              return Pair.second == true;
                            })),
        Impl(std::move(Data)) {}

  const bool *lookup(const CallEvent &Call) {
    const bool *Result = Impl.lookup(Call);
    // If it's a function we expected to find, remember that we've found it.
    if (Result && *Result)
      ++Found;
    return Result;
  }

  // Fail the test if we haven't found all the true-calls we were looking for.
  ~ResultMap() { EXPECT_EQ(Found, Total); }
};

// Scan the code body for call expressions and see if we find all calls that
// we were supposed to find ("true" in the provided ResultMap) and that we
// don't find the ones that we weren't supposed to find
// ("false" in the ResultMap).
template <typename MatchedExprT>
class CallDescriptionConsumer : public ExprEngineConsumer {
  ResultMap &RM;
  void performTest(const Decl *D) {
    using namespace ast_matchers;
    using T = MatchedExprT;

    if (!D->hasBody())
      return;

    const StackFrameContext *SFC =
        Eng.getAnalysisDeclContextManager().getStackFrame(D);
    const ProgramStateRef State = Eng.getInitialState(SFC);

    // FIXME: Maybe use std::variant and std::visit for these.
    const auto MatcherCreator = []() {
      if (std::is_same<T, CallExpr>::value)
        return callExpr();
      if (std::is_same<T, CXXConstructExpr>::value)
        return cxxConstructExpr();
      if (std::is_same<T, CXXMemberCallExpr>::value)
        return cxxMemberCallExpr();
      if (std::is_same<T, CXXOperatorCallExpr>::value)
        return cxxOperatorCallExpr();
      llvm_unreachable("Only these expressions are supported for now.");
    };

    const Expr *E = findNode<T>(D, MatcherCreator());

    CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager();
    CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef<CallEvent> {
      if (std::is_base_of<CallExpr, T>::value)
        return CEMgr.getCall(E, State, SFC);
      if (std::is_same<T, CXXConstructExpr>::value)
        return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E),
                                           /*Target=*/nullptr, State, SFC);
      llvm_unreachable("Only these expressions are supported for now.");
    }();

    // If the call actually matched, check if we really expected it to match.
    const bool *LookupResult = RM.lookup(*Call);
    EXPECT_TRUE(!LookupResult || *LookupResult);

    // ResultMap is responsible for making sure that we've found *all* calls.
  }

public:
  CallDescriptionConsumer(CompilerInstance &C,
                          ResultMap &RM)
      : ExprEngineConsumer(C), RM(RM) {}

  bool HandleTopLevelDecl(DeclGroupRef DG) override {
    for (const auto *D : DG)
      performTest(D);
    return true;
  }
};

template <typename MatchedExprT = CallExpr>
class CallDescriptionAction : public ASTFrontendAction {
  ResultMap RM;

public:
  CallDescriptionAction(
      std::initializer_list<std::pair<CallDescription, bool>> Data)
      : RM(Data) {}

  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
                                                 StringRef File) override {
    return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler,
                                                                   RM);
  }
};

TEST(CallDescription, SimpleNameMatching) {
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{"bar"}, false}, // false: there's no call to 'bar' in this code.
          {{"foo"}, true},  // true: there's a call to 'foo' in this code.
      })),
      "void foo(); void bar() { foo(); }"));
}

TEST(CallDescription, RequiredArguments) {
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{"foo", 1}, true},
          {{"foo", 2}, false},
      })),
      "void foo(int); void foo(int, int); void bar() { foo(1); }"));
}

TEST(CallDescription, LackOfRequiredArguments) {
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{"foo", None}, true},
          {{"foo", 2}, false},
      })),
      "void foo(int); void foo(int, int); void bar() { foo(1); }"));
}

constexpr StringRef MockStdStringHeader = R"code(
  namespace std { inline namespace __1 {
    template<typename T> class basic_string {
      class Allocator {};
    public:
      basic_string();
      explicit basic_string(const char*, const Allocator & = Allocator());
      ~basic_string();
      T *c_str();
    };
  } // namespace __1
  using string = __1::basic_string<char>;
  } // namespace std
)code";

TEST(CallDescription, QualifiedNames) {
  constexpr StringRef AdditionalCode = R"code(
    void foo() {
      using namespace std;
      basic_string<char> s;
      s.c_str();
    })code";
  const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{{"std", "basic_string", "c_str"}}, true},
      })),
      Code));
}

TEST(CallDescription, MatchConstructor) {
  constexpr StringRef AdditionalCode = R"code(
    void foo() {
      using namespace std;
      basic_string<char> s("hello");
    })code";
  const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(
          new CallDescriptionAction<CXXConstructExpr>({
              {{{"std", "basic_string", "basic_string"}, 2, 2}, true},
          })),
      Code));
}

// FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"}
//        This feature is actually implemented, but the test infra is not yet
//        sophisticated enough for testing this. To do that, we will need to
//        implement a much more advanced dispatching mechanism using the CFG for
//        the implicit destructor events.

TEST(CallDescription, MatchConversionOperator) {
  constexpr StringRef Code = R"code(
    namespace aaa {
    namespace bbb {
    struct Bar {
      operator int();
    };
    } // bbb
    } // aaa
    void foo() {
      aaa::bbb::Bar x;
      int tmp = x;
    })code";
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{{"aaa", "bbb", "Bar", "operator int"}}, true},
      })),
      Code));
}

TEST(CallDescription, RejectOverQualifiedNames) {
  constexpr auto Code = R"code(
    namespace my {
    namespace std {
      struct container {
        const char *data() const;
      };
    } // namespace std
    } // namespace my

    void foo() {
      using namespace my;
      std::container v;
      v.data();
    })code";

  // FIXME: We should **not** match.
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{{"std", "container", "data"}}, true},
      })),
      Code));
}

TEST(CallDescription, DontSkipNonInlineNamespaces) {
  constexpr auto Code = R"code(
    namespace my {
    /*not inline*/ namespace v1 {
      void bar();
    } // namespace v1
    } // namespace my
    void foo() {
      my::v1::bar();
    })code";

  {
    SCOPED_TRACE("my v1 bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"my", "v1", "bar"}}, true},
        })),
        Code));
  }
  {
    // FIXME: We should **not** skip non-inline namespaces.
    SCOPED_TRACE("my bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"my", "bar"}}, true},
        })),
        Code));
  }
}

TEST(CallDescription, SkipTopInlineNamespaces) {
  constexpr auto Code = R"code(
    inline namespace my {
    namespace v1 {
      void bar();
    } // namespace v1
    } // namespace my
    void foo() {
      using namespace v1;
      bar();
    })code";

  {
    SCOPED_TRACE("my v1 bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"my", "v1", "bar"}}, true},
        })),
        Code));
  }
  {
    SCOPED_TRACE("v1 bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"v1", "bar"}}, true},
        })),
        Code));
  }
}

TEST(CallDescription, SkipAnonimousNamespaces) {
  constexpr auto Code = R"code(
    namespace {
    namespace std {
    namespace {
    inline namespace {
      struct container {
        const char *data() const { return nullptr; };
      };
    } // namespace inline anonymous
    } // namespace anonymous
    } // namespace std
    } // namespace anonymous

    void foo() {
      std::container v;
      v.data();
    })code";

  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{{"std", "container", "data"}}, true},
      })),
      Code));
}

TEST(CallDescription, AliasNames) {
  constexpr StringRef AliasNamesCode = R"code(
  namespace std {
    struct container {
      const char *data() const;
    };
    using cont = container;
  } // std
)code";

  constexpr StringRef UseAliasInSpelling = R"code(
    void foo() {
      std::cont v;
      v.data();
    })code";
  constexpr StringRef UseStructNameInSpelling = R"code(
    void foo() {
      std::container v;
      v.data();
    })code";
  const std::string UseAliasInSpellingCode =
      (Twine{AliasNamesCode} + UseAliasInSpelling).str();
  const std::string UseStructNameInSpellingCode =
      (Twine{AliasNamesCode} + UseStructNameInSpelling).str();

  // Test if the code spells the alias, wile we match against the struct name,
  // and again matching against the alias.
  {
    SCOPED_TRACE("Using alias in spelling");
    {
      SCOPED_TRACE("std container data");
      EXPECT_TRUE(tooling::runToolOnCode(
          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
              {{{"std", "container", "data"}}, true},
          })),
          UseAliasInSpellingCode));
    }
    {
      // FIXME: We should be able to see-through aliases.
      SCOPED_TRACE("std cont data");
      EXPECT_TRUE(tooling::runToolOnCode(
          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
              {{{"std", "cont", "data"}}, false},
          })),
          UseAliasInSpellingCode));
    }
  }

  // Test if the code spells the struct name, wile we match against the struct
  // name, and again matching against the alias.
  {
    SCOPED_TRACE("Using struct name in spelling");
    {
      SCOPED_TRACE("std container data");
      EXPECT_TRUE(tooling::runToolOnCode(
          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
              {{{"std", "container", "data"}}, true},
          })),
          UseAliasInSpellingCode));
    }
    {
      // FIXME: We should be able to see-through aliases.
      SCOPED_TRACE("std cont data");
      EXPECT_TRUE(tooling::runToolOnCode(
          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
              {{{"std", "cont", "data"}}, false},
          })),
          UseAliasInSpellingCode));
    }
  }
}

TEST(CallDescription, AliasSingleNamespace) {
  constexpr StringRef Code = R"code(
    namespace aaa {
    namespace bbb {
    namespace ccc {
      void bar();
    }} // namespace bbb::ccc
    namespace bbb_alias = bbb;
    } // namespace aaa
    void foo() {
      aaa::bbb_alias::ccc::bar();
    })code";
  {
    SCOPED_TRACE("aaa bbb ccc bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"aaa", "bbb", "ccc", "bar"}}, true},
        })),
        Code));
  }
  {
    // FIXME: We should be able to see-through namespace aliases.
    SCOPED_TRACE("aaa bbb_alias ccc bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"aaa", "bbb_alias", "ccc", "bar"}}, false},
        })),
        Code));
  }
}

TEST(CallDescription, AliasMultipleNamespaces) {
  constexpr StringRef Code = R"code(
    namespace aaa {
    namespace bbb {
    namespace ccc {
      void bar();
    }}} // namespace aaa::bbb::ccc
    namespace aaa_bbb_ccc = aaa::bbb::ccc;
    void foo() {
      using namespace aaa_bbb_ccc;
      bar();
    })code";
  {
    SCOPED_TRACE("aaa bbb ccc bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"aaa", "bbb", "ccc", "bar"}}, true},
        })),
        Code));
  }
  {
    // FIXME: We should be able to see-through namespace aliases.
    SCOPED_TRACE("aaa_bbb_ccc bar");
    EXPECT_TRUE(tooling::runToolOnCode(
        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
            {{{"aaa_bbb_ccc", "bar"}}, false},
        })),
        Code));
  }
}

TEST(CallDescription, NegativeMatchQualifiedNames) {
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
          {{{"foo", "bar"}}, false},
          {{{"bar", "foo"}}, false},
          {{"foo"}, true},
      })),
      "void foo(); struct bar { void foo(); }; void test() { foo(); }"));
}

TEST(CallDescription, MatchBuiltins) {
  // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins.
  EXPECT_TRUE(tooling::runToolOnCode(
      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
          {{{"memset", 3}, false}, {{CDF_MaybeBuiltin, "memset", 3}, true}})),
      "void foo() {"
      "  int x;"
      "  __builtin___memset_chk(&x, 0, sizeof(x),"
      "                         __builtin_object_size(&x, 0));"
      "}"));
}

} // namespace
} // namespace ento
} // namespace clang
