//===- CallDescription.cpp - function/method call matching     --*- 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
//
//===----------------------------------------------------------------------===//
//
/// \file This file defines a generic mechanism for matching for function and
/// method calls of C, C++, and Objective-C languages. Instances of these
/// classes are frequently used together with the CallEvent classes.
//
//===----------------------------------------------------------------------===//

#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/AST/Decl.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "llvm/ADT/ArrayRef.h"
#include <iterator>
#include <optional>

using namespace llvm;
using namespace clang;

using MaybeCount = std::optional<unsigned>;

// A constructor helper.
static MaybeCount readRequiredParams(MaybeCount RequiredArgs,
                                     MaybeCount RequiredParams) {
  if (RequiredParams)
    return RequiredParams;
  if (RequiredArgs)
    return RequiredArgs;
  return std::nullopt;
}

ento::CallDescription::CallDescription(CallDescriptionFlags Flags,
                                       ArrayRef<StringRef> QualifiedName,
                                       MaybeCount RequiredArgs /*= None*/,
                                       MaybeCount RequiredParams /*= None*/)
    : RequiredArgs(RequiredArgs),
      RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)),
      Flags(Flags) {
  assert(!QualifiedName.empty());
  this->QualifiedName.reserve(QualifiedName.size());
  llvm::transform(QualifiedName, std::back_inserter(this->QualifiedName),
                  [](StringRef From) { return From.str(); });
}

/// Construct a CallDescription with default flags.
ento::CallDescription::CallDescription(ArrayRef<StringRef> QualifiedName,
                                       MaybeCount RequiredArgs /*= None*/,
                                       MaybeCount RequiredParams /*= None*/)
    : CallDescription(CDF_None, QualifiedName, RequiredArgs, RequiredParams) {}

bool ento::CallDescription::matches(const CallEvent &Call) const {
  // FIXME: Add ObjC Message support.
  if (Call.getKind() == CE_ObjCMessage)
    return false;

  const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
  if (!FD)
    return false;

  return matchesImpl(FD, Call.getNumArgs(), Call.parameters().size());
}

bool ento::CallDescription::matchesAsWritten(const CallExpr &CE) const {
  const auto *FD = dyn_cast_or_null<FunctionDecl>(CE.getCalleeDecl());
  if (!FD)
    return false;

  return matchesImpl(FD, CE.getNumArgs(), FD->param_size());
}

bool ento::CallDescription::matchesImpl(const FunctionDecl *Callee,
                                        size_t ArgCount,
                                        size_t ParamCount) const {
  const auto *FD = Callee;
  if (!FD)
    return false;

  if (Flags & CDF_MaybeBuiltin) {
    return CheckerContext::isCLibraryFunction(FD, getFunctionName()) &&
           (!RequiredArgs || *RequiredArgs <= ArgCount) &&
           (!RequiredParams || *RequiredParams <= ParamCount);
  }

  if (!II) {
    II = &FD->getASTContext().Idents.get(getFunctionName());
  }

  const auto MatchNameOnly = [](const CallDescription &CD,
                                const NamedDecl *ND) -> bool {
    DeclarationName Name = ND->getDeclName();
    if (const auto *II = Name.getAsIdentifierInfo())
      return II == *CD.II; // Fast case.

    // Fallback to the slow stringification and comparison for:
    // C++ overloaded operators, constructors, destructors, etc.
    // FIXME This comparison is way SLOWER than comparing pointers.
    // At some point in the future, we should compare FunctionDecl pointers.
    return Name.getAsString() == CD.getFunctionName();
  };

  const auto ExactMatchArgAndParamCounts =
      [](size_t ArgCount, size_t ParamCount,
         const CallDescription &CD) -> bool {
    const bool ArgsMatch = !CD.RequiredArgs || *CD.RequiredArgs == ArgCount;
    const bool ParamsMatch =
        !CD.RequiredParams || *CD.RequiredParams == ParamCount;
    return ArgsMatch && ParamsMatch;
  };

  const auto MatchQualifiedNameParts = [](const CallDescription &CD,
                                          const Decl *D) -> bool {
    const auto FindNextNamespaceOrRecord =
        [](const DeclContext *Ctx) -> const DeclContext * {
      while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx))
        Ctx = Ctx->getParent();
      return Ctx;
    };

    auto QualifierPartsIt = CD.begin_qualified_name_parts();
    const auto QualifierPartsEndIt = CD.end_qualified_name_parts();

    // Match namespace and record names. Skip unrelated names if they don't
    // match.
    const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext());
    for (; Ctx && QualifierPartsIt != QualifierPartsEndIt;
         Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) {
      // If not matched just continue and try matching for the next one.
      if (cast<NamedDecl>(Ctx)->getName() != *QualifierPartsIt)
        continue;
      ++QualifierPartsIt;
    }

    // We matched if we consumed all expected qualifier segments.
    return QualifierPartsIt == QualifierPartsEndIt;
  };

  // Let's start matching...
  if (!ExactMatchArgAndParamCounts(ArgCount, ParamCount, *this))
    return false;

  if (!MatchNameOnly(*this, FD))
    return false;

  if (!hasQualifiedNameParts())
    return true;

  return MatchQualifiedNameParts(*this, FD);
}

ento::CallDescriptionSet::CallDescriptionSet(
    std::initializer_list<CallDescription> &&List) {
  Impl.LinearMap.reserve(List.size());
  for (const CallDescription &CD : List)
    Impl.LinearMap.push_back({CD, /*unused*/ true});
}

bool ento::CallDescriptionSet::contains(const CallEvent &Call) const {
  return static_cast<bool>(Impl.lookup(Call));
}

bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const {
  return static_cast<bool>(Impl.lookupAsWritten(CE));
}
