/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef CustomMatchers_h_
#define CustomMatchers_h_

#include "MemMoveAnnotation.h"
#include "Utils.h"

#if CLANG_VERSION_FULL >= 1300
// Starting with clang-13 Expr::isRValue has been renamed to Expr::isPRValue
#define isRValue isPRValue
#endif

namespace clang {
namespace ast_matchers {

/// This matcher will match any function declaration that is declared as a heap
/// allocator.
AST_MATCHER(VarDecl, hasMozGlobalType) {
  if(auto * TD = Node.getType().getTypePtr()->getAsTagDecl()) {
    if(hasCustomAttribute<moz_global_class>(TD))
      return true;
  }
  return false;
}

/// Match any variable declared with the MOZ_RUNINIT qualifier.
AST_MATCHER(VarDecl, isMozGlobal) {
  return hasCustomAttribute<moz_global_var>(&Node);
}

/// Match any variable declared with the MOZ_GLOBINIT qualifier.
AST_MATCHER(VarDecl, isMozGenerated) {
  return hasCustomAttribute<moz_generated>(&Node);
}

/// Match any variable declared with the constinit qualifier.
AST_MATCHER(VarDecl, hasConstInitAttr) {
  return Node.hasAttr<ConstInitAttr>();
}

/// This matcher will match any function declaration that is declared as a heap
/// allocator.
AST_MATCHER(FunctionDecl, heapAllocator) {
  return hasCustomAttribute<moz_heap_allocator>(&Node);
}

/// This matcher will match any declaration that is marked as not accepting
/// arithmetic expressions in its arguments.
AST_MATCHER(Decl, noArithmeticExprInArgs) {
  return hasCustomAttribute<moz_no_arith_expr_in_arg>(&Node);
}

/// This matcher will match any C++ class that is marked as having a trivial
/// constructor and destructor.
AST_MATCHER(CXXRecordDecl, hasTrivialCtorDtor) {
  return hasCustomAttribute<moz_trivial_ctor_dtor>(&Node);
}

/// This matcher will match any C++ class that is marked as having a trivial
/// destructor.
AST_MATCHER(CXXRecordDecl, hasTrivialDtor) {
  return hasCustomAttribute<moz_trivial_dtor>(&Node);
}

AST_MATCHER(CXXConstructExpr, allowsTemporary) {
  return hasCustomAttribute<moz_allow_temporary>(Node.getConstructor());
}

/// This matcher will match lvalue-ref-qualified methods.
AST_MATCHER(CXXMethodDecl, isLValueRefQualified) {
  return Node.getRefQualifier() == RQ_LValue;
}

/// This matcher will match rvalue-ref-qualified methods.
AST_MATCHER(CXXMethodDecl, isRValueRefQualified) {
  return Node.getRefQualifier() == RQ_RValue;
}

AST_POLYMORPHIC_MATCHER(isFirstParty,
                        AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) {
  return !inThirdPartyPath(&Node, &Finder->getASTContext()) &&
         !ASTIsInSystemHeader(Finder->getASTContext(), Node);
}

AST_MATCHER(DeclaratorDecl, isNotSpiderMonkey) {
  // Detect SpiderMonkey path. Not as strict as isFirstParty, but this is
  // expected to disappear soon by getting a common style guide between DOM and
  // SpiderMonkey.
  std::string Path = Node.getBeginLoc().printToString(
      Finder->getASTContext().getSourceManager());
  return Path.find("js") == std::string::npos &&
         Path.find("xpc") == std::string::npos &&
         Path.find("XPC") == std::string::npos;
}

/// This matcher will match any declaration with an initializer that's
/// considered as constant by the compiler.
AST_MATCHER(VarDecl, hasConstantInitializer) {
  if (Node.hasInit()) {
    if (const auto *CE = dyn_cast<CXXConstructExpr>(Node.getInit())) {
      if (const auto *CD = CE->getConstructor();
          CD && CD->isDefined() && CD->isDefaultConstructor()) {
        const CXXRecordDecl *CR = CD->getParent();
        if (CR->hasTrivialDefaultConstructor())
          return true;
        if (CR->hasConstexprDefaultConstructor())
          return true;

        // This sorts our several situations like virtual bases etc.
        if (CR->defaultedDefaultConstructorIsConstexpr()) {
          // Only accept constructor with empty body and constant initializer
          // for each member initialization.
          if (const auto *CS = dyn_cast<CompoundStmt>(CD->getBody()); CS && CS->body_empty()) {
            bool AllCustomInitializerCorrect = true;
            for (const CXXCtorInitializer *CI : CD->inits()) {
              bool ForRef = CI->isAnyMemberInitializer()
                          ? CI->getAnyMember()->getType()->isReferenceType()
                          : false;
              if (!CI->getInit()->isConstantInitializer(Finder->getASTContext(), ForRef)) {
                AllCustomInitializerCorrect = false;
                break;
              }
            }
            if (AllCustomInitializerCorrect)
              return true;
          }
        }
      }
    }

    bool ForRef = Node.getType()->isReferenceType();
    return Node.getInit()->isConstantInitializer(Finder->getASTContext(), ForRef);
  } else if (Node.hasConstantInitialization()) {
    return true;
  } else {
    return Node.getType().isTrivialType(Finder->getASTContext());
  }
}

/// This matcher will match temporary expressions.
/// We need this matcher for compatibility with clang 3.* (clang 4 and above
/// insert a MaterializeTemporaryExpr everywhere).
AST_MATCHER(Expr, isTemporary) {
  return Node.isRValue() || Node.isXValue() ||
         isa<MaterializeTemporaryExpr>(&Node);
}

/// This matcher will match any method declaration that is marked as returning
/// a pointer deleted by the destructor of the class.
AST_MATCHER(CXXMethodDecl, noDanglingOnTemporaries) {
  return hasCustomAttribute<moz_no_dangling_on_temporaries>(&Node);
}

/// This matcher will match any function declaration that is marked to prohibit
/// calling AddRef or Release on its return value.
AST_MATCHER(FunctionDecl, hasNoAddRefReleaseOnReturnAttr) {
  return hasCustomAttribute<moz_no_addref_release_on_return>(&Node);
}

AST_MATCHER(Decl, isInterestingForImplicitConversion) {
  return isInterestingDeclForImplicitConversion(&Node);
}

AST_MATCHER(VarDecl, isReferenced) { return Node.isReferenced(); }

AST_MATCHER(VarDecl, isParameter) {
  return isa<ImplicitParamDecl>(Node) || isa<ParmVarDecl>(Node);
}

/// This matcher will match any function declaration that is marked as being
/// allowed to run script.
AST_MATCHER(FunctionDecl, hasCanRunScriptAnnotation) {
  return hasCustomAttribute<moz_can_run_script>(&Node);
}

/// This matcher will match all arithmetic binary operators.
AST_MATCHER(BinaryOperator, binaryArithmeticOperator) {
  BinaryOperatorKind OpCode = Node.getOpcode();
  return OpCode == BO_Mul || OpCode == BO_Div || OpCode == BO_Rem ||
         OpCode == BO_Add || OpCode == BO_Sub || OpCode == BO_Shl ||
         OpCode == BO_Shr || OpCode == BO_And || OpCode == BO_Xor ||
         OpCode == BO_Or || OpCode == BO_MulAssign || OpCode == BO_DivAssign ||
         OpCode == BO_RemAssign || OpCode == BO_AddAssign ||
         OpCode == BO_SubAssign || OpCode == BO_ShlAssign ||
         OpCode == BO_ShrAssign || OpCode == BO_AndAssign ||
         OpCode == BO_XorAssign || OpCode == BO_OrAssign;
}

/// This matcher will match all arithmetic unary operators.
AST_MATCHER(UnaryOperator, unaryArithmeticOperator) {
  UnaryOperatorKind OpCode = Node.getOpcode();
  return OpCode == UO_PostInc || OpCode == UO_PostDec || OpCode == UO_PreInc ||
         OpCode == UO_PreDec || OpCode == UO_Plus || OpCode == UO_Minus ||
         OpCode == UO_Not;
}

/// This matcher will match the unary dereference operator
AST_MATCHER(UnaryOperator, unaryDereferenceOperator) {
  UnaryOperatorKind OpCode = Node.getOpcode();
  return OpCode == UO_Deref;
}

/// This matcher will match == and != binary operators.
AST_MATCHER(BinaryOperator, binaryEqualityOperator) {
  BinaryOperatorKind OpCode = Node.getOpcode();
  return OpCode == BO_EQ || OpCode == BO_NE;
}

/// This matcher will match comma operator.
AST_MATCHER(BinaryOperator, binaryCommaOperator) {
  BinaryOperatorKind OpCode = Node.getOpcode();
  return OpCode == BO_Comma;
}

/// This matcher will match floating point types.
AST_MATCHER(QualType, isFloat) { return Node->isRealFloatingType(); }

/// This matcher will match locations in system headers.  This is adopted from
/// isExpansionInSystemHeader in newer clangs, but modified in order to work
/// with old clangs that we use on infra.
AST_POLYMORPHIC_MATCHER(isInSystemHeader,
                        AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) {
  return ASTIsInSystemHeader(Finder->getASTContext(), Node);
}

/// This matcher will match a file "gtest-port.h". The file contains
/// known fopen usages that are OK.
AST_MATCHER(CallExpr, isInWhitelistForFopenUsage) {
  static const char Whitelist[] = "gtest-port.h";
  SourceLocation Loc = Node.getBeginLoc();
  StringRef FileName =
      getFilename(Finder->getASTContext().getSourceManager(), Loc);

  return *llvm::sys::path::rbegin(FileName) == Whitelist;
}

/// This matcher will match a list of files.  These files contain
/// known NaN-testing expressions which we would like to whitelist.
AST_MATCHER(BinaryOperator, isInWhitelistForNaNExpr) {
  const char *whitelist[] = {"SkScalar.h", "json_writer.cpp", "State.cpp"};

  SourceLocation Loc = Node.getOperatorLoc();
  StringRef FileName =
      getFilename(Finder->getASTContext().getSourceManager(), Loc);
  for (auto itr = std::begin(whitelist); itr != std::end(whitelist); itr++) {
    if (*llvm::sys::path::rbegin(FileName) == *itr) {
      return true;
    }
  }

  return false;
}

AST_MATCHER(CallExpr, isInWhiteListForPrincipalGetUri) {
  const auto Whitelist = {"nsIPrincipal.h", "BasePrincipal.cpp",
                          "ContentPrincipal.cpp"};
  SourceLocation Loc = Node.getBeginLoc();
  StringRef Filename =
      getFilename(Finder->getASTContext().getSourceManager(), Loc);

  for (auto Exclusion : Whitelist) {
    if (Filename.find(Exclusion) != std::string::npos) {
      return true;
    }
  }
  return false;
}

/// This matcher will match a list of files which contain NS_NewNamedThread
/// code or names of existing threads that we would like to ignore.
AST_MATCHER(CallExpr, isInAllowlistForThreads) {

  // Get the source location of the call.
  SourceLocation Loc = Node.getRParenLoc();
  StringRef FileName =
      getFilename(Finder->getASTContext().getSourceManager(), Loc);

  const auto rbegin = [](StringRef s) { return llvm::sys::path::rbegin(s); };
  const auto rend = [](StringRef s) { return llvm::sys::path::rend(s); };

  // Files in the allowlist are (definitionally) explicitly permitted to create
  // new threads.
  for (auto thread_file : allow_thread_files) {
    // All the provided path-elements must match.
    const bool match = [&] {
      auto it1 = rbegin(FileName), it2 = rbegin(thread_file),
           end1 = rend(FileName), end2 = rend(thread_file);
      for (; it2 != end2; ++it1, ++it2) {
        if (it1 == end1 || *it1 != *it2) {
          return false;
        }
      }
      return true;
    }();
    if (match) {
      return true;
    }
  }

  // Check the first arg (the name of the thread).
  const StringLiteral *nameArg =
      dyn_cast<StringLiteral>(Node.getArg(0)->IgnoreImplicit());
  if (nameArg) {
    const StringRef name = nameArg->getString();
    for (auto thread_name : allow_thread_names) {
      if (name == thread_name) {
        return true;
      }
    }
  }

  return false;
}

/// This matcher will match all accesses to AddRef or Release methods.
AST_MATCHER(MemberExpr, isAddRefOrRelease) {
  ValueDecl *Member = Node.getMemberDecl();
  CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Member);
  if (Method) {
    const auto &Name = getNameChecked(Method);
    return Name == "AddRef" || Name == "Release";
  }
  return false;
}

/// This matcher will select classes which are refcounted AND have an mRefCnt
/// member.
AST_MATCHER(CXXRecordDecl, hasRefCntMember) {
  return isClassRefCounted(&Node) && getClassRefCntMember(&Node);
}

/// This matcher will select classes which are refcounted.
AST_MATCHER(CXXRecordDecl, isRefCounted) { return isClassRefCounted(&Node); }

AST_MATCHER(QualType, hasVTable) { return typeHasVTable(Node); }

AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) {
  return hasCustomAttribute<moz_needs_no_vtable_type>(&Node);
}

AST_MATCHER(CXXRecordDecl, hasTrivialDestructor) {
  return Node.hasTrivialDestructor();
}

AST_MATCHER(CXXRecordDecl, hasTrivialDefaultConstructor) {
  return Node.hasTrivialDefaultConstructor();
}

AST_MATCHER(CXXRecordDecl, hasConstexprDefaultConstructor) {
  return Node.hasConstexprDefaultConstructor();
}

/// This matcher will select classes which are non-memmovable
AST_MATCHER(QualType, isNonMemMovable) {
  return NonMemMovable.hasEffectiveAnnotation(Node);
}

/// This matcher will select classes which require a memmovable template arg
AST_MATCHER(CXXRecordDecl, needsMemMovableTemplateArg) {
  return hasCustomAttribute<moz_needs_memmovable_type>(&Node);
}

/// This matcher will select classes which require all members to be memmovable
AST_MATCHER(CXXRecordDecl, needsMemMovableMembers) {
  return hasCustomAttribute<moz_needs_memmovable_members>(&Node);
}

AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) {
  const CXXConstructorDecl *Declaration = Node.getCanonicalDecl();
  return
      // Skip constructors in system headers
      !ASTIsInSystemHeader(Declaration->getASTContext(), *Declaration) &&
      // Skip ignored namespaces and paths
      !isInIgnoredNamespaceForImplicitCtor(Declaration) &&
      !inThirdPartyPath(Declaration) &&
      // We only want Converting constructors
      Declaration->isConvertingConstructor(false) &&
      // We don't want copy of move constructors, as those are allowed to be
      // implicit
      !Declaration->isCopyOrMoveConstructor() &&
      // We don't want inheriting constructors, since using declarations can't
      // have attributes
      !Declaration->isInheritingConstructor() &&
      // We don't want deleted constructors.
      !Declaration->isDeleted();
}

AST_MATCHER_P(Expr, ignoreTrivials, internal::Matcher<Expr>, InnerMatcher) {
  return InnerMatcher.matches(*IgnoreTrivials(&Node), Finder, Builder);
}

// Takes two matchers: the first one is a condition; the second is a matcher to
// be applied once we are done unwrapping trivials.  While the condition does
// not match and we're looking at a trivial, will keep unwrapping the trivial
// and trying again. Once the condition matches, we will go ahead and unwrap all
// trivials and apply the inner matcher to the result.
//
// The expected use here is if we want to condition a match on some typecheck
// but apply the match to only non-trivials, because there are trivials (e.g.
// casts) that can change types.
AST_MATCHER_P2(Expr, ignoreTrivialsConditional, internal::Matcher<Expr>,
               Condition, internal::Matcher<Expr>, InnerMatcher) {
  const Expr *node = &Node;
  while (true) {
    if (Condition.matches(*node, Finder, Builder)) {
      return InnerMatcher.matches(*IgnoreTrivials(node), Finder, Builder);
    }
    const Expr *newNode = MaybeSkipOneTrivial(node);
    if (newNode == node) {
      return false;
    }
    node = newNode;
  }
}

// We can't call this "isImplicit" since it clashes with an existing matcher in
// clang.
AST_MATCHER(FunctionDecl, isMarkedImplicit) {
  return hasCustomAttribute<moz_implicit>(&Node);
}
AST_MATCHER(FunctionDecl, isMarkedMustOverride) {
  return hasCustomAttribute<moz_must_override>(&Node);
}

AST_MATCHER(CXXRecordDecl, isConcreteClass) { return !Node.isAbstract(); }

AST_MATCHER(QualType, autoNonAutoableType) {
  if (const AutoType *T = Node->getContainedAutoType()) {
    if (const CXXRecordDecl *Rec = T->getAsCXXRecordDecl()) {
      return hasCustomAttribute<moz_non_autoable>(Rec);
    }
  }
  return false;
}

AST_MATCHER(CXXConstructorDecl, isExplicitMoveConstructor) {
  return Node.isExplicit() && Node.isMoveConstructor();
}

AST_MATCHER(CXXConstructorDecl, isCompilerProvidedCopyConstructor) {
  return !Node.isUserProvided() && Node.isCopyConstructor();
}

AST_MATCHER(CallExpr, isSnprintfLikeFunc) {
  static const std::string Snprintf = "snprintf";
  static const std::string Vsnprintf = "vsnprintf";
  const FunctionDecl *Func = Node.getDirectCallee();

  if (!Func || isa<CXXMethodDecl>(Func)) {
    return false;
  }

  StringRef Name = getNameChecked(Func);
  if (Name != Snprintf && Name != Vsnprintf) {
    return false;
  }

  return !inThirdPartyPath(Node.getBeginLoc(),
                           Finder->getASTContext().getSourceManager()) &&
         !isIgnoredPathForSprintfLiteral(
             &Node, Finder->getASTContext().getSourceManager());
}

AST_MATCHER(CXXRecordDecl, isLambdaDecl) { return Node.isLambda(); }

AST_MATCHER(QualType, isRefPtr) { return typeIsRefPtr(Node); }

AST_MATCHER(QualType, isSmartPtrToRefCounted) {
  auto *D = getNonTemplateSpecializedCXXRecordDecl(Node);
  if (!D) {
    return false;
  }

  D = D->getCanonicalDecl();

  return D && hasCustomAttribute<moz_is_smartptr_to_refcounted>(D);
}

AST_MATCHER(ClassTemplateSpecializationDecl, isSmartPtrToRefCountedDecl) {
  auto *D = dyn_cast_or_null<CXXRecordDecl>(
      Node.getSpecializedTemplate()->getTemplatedDecl());
  if (!D) {
    return false;
  }

  D = D->getCanonicalDecl();

  return D && hasCustomAttribute<moz_is_smartptr_to_refcounted>(D);
}

AST_MATCHER(CXXRecordDecl, hasBaseClasses) {
  const CXXRecordDecl *Decl = Node.getCanonicalDecl();

  // Must have definition and should inherit other classes
  return Decl && Decl->hasDefinition() && Decl->getNumBases();
}

AST_MATCHER(CXXMethodDecl, isRequiredBaseMethod) {
  const CXXMethodDecl *Decl = Node.getCanonicalDecl();
  return Decl && hasCustomAttribute<moz_required_base_method>(Decl);
}

AST_MATCHER(CXXMethodDecl, isNonVirtual) {
  const CXXMethodDecl *Decl = Node.getCanonicalDecl();
  return Decl && !Decl->isVirtual();
}

AST_MATCHER(FunctionDecl, isMozMustReturnFromCaller) {
  const FunctionDecl *Decl = Node.getCanonicalDecl();
  return Decl &&
         hasCustomAttribute<moz_must_return_from_caller_if_this_is_arg>(Decl);
}

/// This matcher will select default args which have nullptr as the value.
AST_MATCHER(CXXDefaultArgExpr, isNullDefaultArg) {
  const Expr *Expr = Node.getExpr();
  return Expr && Expr->isNullPointerConstant(Finder->getASTContext(),
                                             Expr::NPC_NeverValueDependent);
}

AST_MATCHER(UsingDirectiveDecl, isUsingNamespaceMozillaJava) {
  const NamespaceDecl *Namespace = Node.getNominatedNamespace();
  const std::string &FQName = Namespace->getQualifiedNameAsString();

  static const char NAMESPACE[] = "mozilla::java";
  static const char PREFIX[] = "mozilla::java::";

  // We match both the `mozilla::java` namespace itself as well as any other
  // namespaces contained within the `mozilla::java` namespace.
  return !FQName.compare(NAMESPACE) ||
         !FQName.compare(0, sizeof(PREFIX) - 1, PREFIX);
}

AST_MATCHER(MemberExpr, hasKnownLiveAnnotation) {
  ValueDecl *Member = Node.getMemberDecl();
  FieldDecl *Field = dyn_cast<FieldDecl>(Member);
  return Field && hasCustomAttribute<moz_known_live>(Field);
}

#define GENERATE_JSTYPEDEF_PAIR(templateName)                                  \
  {templateName "Function", templateName "<JSFunction*>"},                     \
      {templateName "Id", templateName "<JS::PropertyKey>"},                   \
      {templateName "Object", templateName "<JSObject*>"},                     \
      {templateName "Script", templateName "<JSScript*>"},                     \
      {templateName "String", templateName "<JSString*>"},                     \
      {templateName "Symbol", templateName "<JS::Symbol*>"},                   \
      {templateName "BigInt", templateName "<JS::BigInt*>"},                   \
      {templateName "Value", templateName "<JS::Value>"},                      \
      {templateName "ValueVector", templateName "Vector<JS::Value>"},          \
      {templateName "ObjectVector", templateName "Vector<JSObject*>"}, {       \
    templateName "IdVector", templateName "Vector<JS::PropertyKey>"            \
  }

static const char *const JSHandleRootedTypedefMap[][2] = {
    GENERATE_JSTYPEDEF_PAIR("JS::Handle"),
    GENERATE_JSTYPEDEF_PAIR("JS::MutableHandle"),
    GENERATE_JSTYPEDEF_PAIR("JS::Rooted"),
    // Technically there is no PersistentRootedValueVector, and that's okay
    GENERATE_JSTYPEDEF_PAIR("JS::PersistentRooted"),
};

AST_MATCHER(DeclaratorDecl, isUsingJSHandleRootedTypedef) {
  QualType Type = Node.getType();
  std::string TypeName = Type.getAsString();
  for (auto &pair : JSHandleRootedTypedefMap) {
    if (!TypeName.compare(pair[0])) {
      return true;
    }
  }
  return false;
}

} // namespace ast_matchers
} // namespace clang

#undef isRValue
#endif
