/* 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/. */

#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/Version.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Frontend/MultiplexConsumer.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include <memory>

#define CLANG_VERSION_FULL (CLANG_VERSION_MAJOR * 100 + CLANG_VERSION_MINOR)

using namespace llvm;
using namespace clang;

#if CLANG_VERSION_FULL >= 306
typedef std::unique_ptr<ASTConsumer> ASTConsumerPtr;
#else
typedef ASTConsumer *ASTConsumerPtr;
#endif

namespace {

using namespace clang::ast_matchers;
class DiagnosticsMatcher {
public:
  DiagnosticsMatcher();

  ASTConsumerPtr makeASTConsumer() { return astMatcher.newASTConsumer(); }

private:
  class ScopeChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class ArithmeticArgChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class TrivialCtorDtorChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class NaNExprChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class NoAddRefReleaseOnReturnChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class RefCountedInsideLambdaChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class NoDuplicateRefCntMemberChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class NeedsNoVTableTypeChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class NonMemMovableChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class ExplicitImplicitChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class NoAutoTypeChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class NoExplicitMoveConstructorChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  class RefCountedCopyConstructorChecker : public MatchFinder::MatchCallback {
  public:
    virtual void run(const MatchFinder::MatchResult &Result);
  };

  ScopeChecker scopeChecker;
  ArithmeticArgChecker arithmeticArgChecker;
  TrivialCtorDtorChecker trivialCtorDtorChecker;
  NaNExprChecker nanExprChecker;
  NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
  RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
  ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
  NoDuplicateRefCntMemberChecker noDuplicateRefCntMemberChecker;
  NeedsNoVTableTypeChecker needsNoVTableTypeChecker;
  NonMemMovableChecker nonMemMovableChecker;
  ExplicitImplicitChecker explicitImplicitChecker;
  NoAutoTypeChecker noAutoTypeChecker;
  NoExplicitMoveConstructorChecker noExplicitMoveConstructorChecker;
  RefCountedCopyConstructorChecker refCountedCopyConstructorChecker;
  MatchFinder astMatcher;
};

namespace {

std::string getDeclarationNamespace(const Decl *decl) {
  const DeclContext *DC =
      decl->getDeclContext()->getEnclosingNamespaceContext();
  const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
  if (!ND) {
    return "";
  }

  while (const DeclContext *ParentDC = ND->getParent()) {
    if (!isa<NamespaceDecl>(ParentDC)) {
      break;
    }
    ND = cast<NamespaceDecl>(ParentDC);
  }

  const auto &name = ND->getName();
  return name;
}

bool isInIgnoredNamespaceForImplicitCtor(const Decl *decl) {
  std::string name = getDeclarationNamespace(decl);
  if (name == "") {
    return false;
  }

  return name == "std" ||               // standard C++ lib
         name == "__gnu_cxx" ||         // gnu C++ lib
         name == "boost" ||             // boost
         name == "webrtc" ||            // upstream webrtc
         name == "rtc" ||               // upstream webrtc 'base' package
         name.substr(0, 4) == "icu_" || // icu
         name == "google" ||            // protobuf
         name == "google_breakpad" ||   // breakpad
         name == "soundtouch" ||        // libsoundtouch
         name == "stagefright" ||       // libstagefright
         name == "MacFileUtilities" ||  // MacFileUtilities
         name == "dwarf2reader" ||      // dwarf2reader
         name == "arm_ex_to_module" ||  // arm_ex_to_module
         name == "testing";             // gtest
}

bool isInIgnoredNamespaceForImplicitConversion(const Decl *decl) {
  std::string name = getDeclarationNamespace(decl);
  if (name == "") {
    return false;
  }

  return name == "std" ||             // standard C++ lib
         name == "__gnu_cxx" ||       // gnu C++ lib
         name == "google_breakpad" || // breakpad
         name == "testing";           // gtest
}

bool isIgnoredPathForImplicitCtor(const Decl *decl) {
  SourceLocation Loc = decl->getLocation();
  const SourceManager &SM = decl->getASTContext().getSourceManager();
  SmallString<1024> FileName = SM.getFilename(Loc);
  llvm::sys::fs::make_absolute(FileName);
  llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName),
                                    end = llvm::sys::path::rend(FileName);
  for (; begin != end; ++begin) {
    if (begin->compare_lower(StringRef("skia")) == 0 ||
        begin->compare_lower(StringRef("angle")) == 0 ||
        begin->compare_lower(StringRef("harfbuzz")) == 0 ||
        begin->compare_lower(StringRef("hunspell")) == 0 ||
        begin->compare_lower(StringRef("scoped_ptr.h")) == 0 ||
        begin->compare_lower(StringRef("graphite2")) == 0) {
      return true;
    }
    if (begin->compare_lower(StringRef("chromium")) == 0) {
      // Ignore security/sandbox/chromium but not ipc/chromium.
      ++begin;
      return begin != end && begin->compare_lower(StringRef("sandbox")) == 0;
    }
  }
  return false;
}

bool isIgnoredPathForImplicitConversion(const Decl *decl) {
  decl = decl->getCanonicalDecl();
  SourceLocation Loc = decl->getLocation();
  const SourceManager &SM = decl->getASTContext().getSourceManager();
  SmallString<1024> FileName = SM.getFilename(Loc);
  llvm::sys::fs::make_absolute(FileName);
  llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName),
                                    end = llvm::sys::path::rend(FileName);
  for (; begin != end; ++begin) {
    if (begin->compare_lower(StringRef("graphite2")) == 0) {
      return true;
    }
  }
  return false;
}

bool isInterestingDeclForImplicitConversion(const Decl *decl) {
  return !isInIgnoredNamespaceForImplicitConversion(decl) &&
         !isIgnoredPathForImplicitConversion(decl);
}

bool isIgnoredExprForMustUse(const Expr *E) {
  if (const CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) {
    switch (OpCall->getOperator()) {
    case OO_Equal:
    case OO_PlusEqual:
    case OO_MinusEqual:
    case OO_StarEqual:
    case OO_SlashEqual:
    case OO_PercentEqual:
    case OO_CaretEqual:
    case OO_AmpEqual:
    case OO_PipeEqual:
    case OO_LessLessEqual:
    case OO_GreaterGreaterEqual:
      return true;
    default:
      return false;
    }
  }

  if (const BinaryOperator *Op = dyn_cast<BinaryOperator>(E)) {
    return Op->isAssignmentOp();
  }

  return false;
}
}

class CustomTypeAnnotation {
  enum ReasonKind {
    RK_None,
    RK_Direct,
    RK_ArrayElement,
    RK_BaseClass,
    RK_Field,
    RK_TemplateInherited,
  };
  struct AnnotationReason {
    QualType Type;
    ReasonKind Kind;
    const FieldDecl *Field;

    bool valid() const { return Kind != RK_None; }
  };
  typedef DenseMap<void *, AnnotationReason> ReasonCache;

  const char *Spelling;
  const char *Pretty;
  ReasonCache Cache;

public:
  CustomTypeAnnotation(const char *Spelling, const char *Pretty)
      : Spelling(Spelling), Pretty(Pretty){};

  virtual ~CustomTypeAnnotation() {}

  // Checks if this custom annotation "effectively affects" the given type.
  bool hasEffectiveAnnotation(QualType T) {
    return directAnnotationReason(T).valid();
  }
  void dumpAnnotationReason(DiagnosticsEngine &Diag, QualType T,
                            SourceLocation Loc);

  void reportErrorIfPresent(DiagnosticsEngine &Diag, QualType T,
                            SourceLocation Loc, unsigned ErrorID,
                            unsigned NoteID) {
    if (hasEffectiveAnnotation(T)) {
      Diag.Report(Loc, ErrorID) << T;
      Diag.Report(Loc, NoteID);
      dumpAnnotationReason(Diag, T, Loc);
    }
  }

private:
  bool hasLiteralAnnotation(QualType T) const;
  AnnotationReason directAnnotationReason(QualType T);

protected:
  // Allow subclasses to apply annotations to external code:
  virtual bool hasFakeAnnotation(const TagDecl *D) const { return false; }
};

static CustomTypeAnnotation StackClass =
    CustomTypeAnnotation("moz_stack_class", "stack");
static CustomTypeAnnotation GlobalClass =
    CustomTypeAnnotation("moz_global_class", "global");
static CustomTypeAnnotation NonHeapClass =
    CustomTypeAnnotation("moz_nonheap_class", "non-heap");
static CustomTypeAnnotation HeapClass =
    CustomTypeAnnotation("moz_heap_class", "heap");
static CustomTypeAnnotation NonTemporaryClass =
    CustomTypeAnnotation("moz_non_temporary_class", "non-temporary");
static CustomTypeAnnotation MustUse =
    CustomTypeAnnotation("moz_must_use", "must-use");

class MemMoveAnnotation final : public CustomTypeAnnotation {
public:
  MemMoveAnnotation()
      : CustomTypeAnnotation("moz_non_memmovable", "non-memmove()able") {}

  virtual ~MemMoveAnnotation() {}

protected:
  bool hasFakeAnnotation(const TagDecl *D) const override {
    // Annotate everything in ::std, with a few exceptions; see bug
    // 1201314 for discussion.
    if (getDeclarationNamespace(D) == "std") {
      // This doesn't check that it's really ::std::pair and not
      // ::std::something_else::pair, but should be good enough.
      StringRef Name = D->getName();
      if (Name == "pair" || Name == "atomic" || Name == "__atomic_base") {
        return false;
      }
      return true;
    }
    return false;
  }
};

static MemMoveAnnotation NonMemMovable = MemMoveAnnotation();

class MozChecker : public ASTConsumer, public RecursiveASTVisitor<MozChecker> {
  DiagnosticsEngine &Diag;
  const CompilerInstance &CI;
  DiagnosticsMatcher matcher;

public:
  MozChecker(const CompilerInstance &CI) : Diag(CI.getDiagnostics()), CI(CI) {}

  ASTConsumerPtr getOtherConsumer() { return matcher.makeASTConsumer(); }

  virtual void HandleTranslationUnit(ASTContext &ctx) {
    TraverseDecl(ctx.getTranslationUnitDecl());
  }

  static bool hasCustomAnnotation(const Decl *D, const char *Spelling) {
    iterator_range<specific_attr_iterator<AnnotateAttr>> Attrs =
        D->specific_attrs<AnnotateAttr>();

    for (AnnotateAttr *Attr : Attrs) {
      if (Attr->getAnnotation() == Spelling) {
        return true;
      }
    }

    return false;
  }

  void HandleUnusedExprResult(const Stmt *stmt) {
    const Expr *E = dyn_cast_or_null<Expr>(stmt);
    if (E) {
      QualType T = E->getType();
      if (MustUse.hasEffectiveAnnotation(T) && !isIgnoredExprForMustUse(E)) {
        unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
            DiagnosticIDs::Error, "Unused value of must-use type %0");

        Diag.Report(E->getLocStart(), errorID) << T;
        MustUse.dumpAnnotationReason(Diag, T, E->getLocStart());
      }
    }
  }

  bool VisitCXXRecordDecl(CXXRecordDecl *d) {
    // We need definitions, not declarations
    if (!d->isThisDeclarationADefinition())
      return true;

    // Look through all of our immediate bases to find methods that need to be
    // overridden
    typedef std::vector<CXXMethodDecl *> OverridesVector;
    OverridesVector must_overrides;
    for (CXXRecordDecl::base_class_iterator base = d->bases_begin(),
                                            e = d->bases_end();
         base != e; ++base) {
      // The base is either a class (CXXRecordDecl) or it's a templated class...
      CXXRecordDecl *parent = base->getType()
                                  .getDesugaredType(d->getASTContext())
                                  ->getAsCXXRecordDecl();
      // The parent might not be resolved to a type yet. In this case, we can't
      // do any checking here. For complete correctness, we should visit
      // template instantiations, but this case is likely to be rare, so we will
      // ignore it until it becomes important.
      if (!parent) {
        continue;
      }
      parent = parent->getDefinition();
      for (CXXRecordDecl::method_iterator M = parent->method_begin();
           M != parent->method_end(); ++M) {
        if (hasCustomAnnotation(*M, "moz_must_override"))
          must_overrides.push_back(*M);
      }
    }

    for (OverridesVector::iterator it = must_overrides.begin();
         it != must_overrides.end(); ++it) {
      bool overridden = false;
      for (CXXRecordDecl::method_iterator M = d->method_begin();
           !overridden && M != d->method_end(); ++M) {
        // The way that Clang checks if a method M overrides its parent method
        // is if the method has the same name but would not overload.
        if (M->getName() == (*it)->getName() &&
            !CI.getSema().IsOverload(*M, (*it), false)) {
          overridden = true;
          break;
        }
      }
      if (!overridden) {
        unsigned overrideID = Diag.getDiagnosticIDs()->getCustomDiagID(
            DiagnosticIDs::Error, "%0 must override %1");
        unsigned overrideNote = Diag.getDiagnosticIDs()->getCustomDiagID(
            DiagnosticIDs::Note, "function to override is here");
        Diag.Report(d->getLocation(), overrideID) << d->getDeclName()
                                                  << (*it)->getDeclName();
        Diag.Report((*it)->getLocation(), overrideNote);
      }
    }

    return true;
  }

  bool VisitSwitchCase(SwitchCase *stmt) {
    HandleUnusedExprResult(stmt->getSubStmt());
    return true;
  }
  bool VisitCompoundStmt(CompoundStmt *stmt) {
    for (CompoundStmt::body_iterator it = stmt->body_begin(),
                                     e = stmt->body_end();
         it != e; ++it) {
      HandleUnusedExprResult(*it);
    }
    return true;
  }
  bool VisitIfStmt(IfStmt *Stmt) {
    HandleUnusedExprResult(Stmt->getThen());
    HandleUnusedExprResult(Stmt->getElse());
    return true;
  }
  bool VisitWhileStmt(WhileStmt *Stmt) {
    HandleUnusedExprResult(Stmt->getBody());
    return true;
  }
  bool VisitDoStmt(DoStmt *Stmt) {
    HandleUnusedExprResult(Stmt->getBody());
    return true;
  }
  bool VisitForStmt(ForStmt *Stmt) {
    HandleUnusedExprResult(Stmt->getBody());
    HandleUnusedExprResult(Stmt->getInit());
    HandleUnusedExprResult(Stmt->getInc());
    return true;
  }
  bool VisitBinComma(BinaryOperator *Op) {
    HandleUnusedExprResult(Op->getLHS());
    return true;
  }
};

/// A cached data of whether classes are refcounted or not.
typedef DenseMap<const CXXRecordDecl *, std::pair<const Decl *, bool>>
    RefCountedMap;
RefCountedMap refCountedClasses;

bool classHasAddRefRelease(const CXXRecordDecl *D) {
  const RefCountedMap::iterator &it = refCountedClasses.find(D);
  if (it != refCountedClasses.end()) {
    return it->second.second;
  }

  bool seenAddRef = false;
  bool seenRelease = false;
  for (CXXRecordDecl::method_iterator method = D->method_begin();
       method != D->method_end(); ++method) {
    const auto &name = method->getName();
    if (name == "AddRef") {
      seenAddRef = true;
    } else if (name == "Release") {
      seenRelease = true;
    }
  }
  refCountedClasses[D] = std::make_pair(D, seenAddRef && seenRelease);
  return seenAddRef && seenRelease;
}

bool isClassRefCounted(QualType T);

bool isClassRefCounted(const CXXRecordDecl *D) {
  // Normalize so that D points to the definition if it exists.
  if (!D->hasDefinition())
    return false;
  D = D->getDefinition();
  // Base class: anyone with AddRef/Release is obviously a refcounted class.
  if (classHasAddRefRelease(D))
    return true;

  // Look through all base cases to figure out if the parent is a refcounted
  // class.
  for (CXXRecordDecl::base_class_const_iterator base = D->bases_begin();
       base != D->bases_end(); ++base) {
    bool super = isClassRefCounted(base->getType());
    if (super) {
      return true;
    }
  }

  return false;
}

bool isClassRefCounted(QualType T) {
  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
    T = arrTy->getElementType();
  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
  return clazz ? isClassRefCounted(clazz) : false;
}

template <class T> bool IsInSystemHeader(const ASTContext &AC, const T &D) {
  auto &SourceManager = AC.getSourceManager();
  auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart());
  if (ExpansionLoc.isInvalid()) {
    return false;
  }
  return SourceManager.isInSystemHeader(ExpansionLoc);
}

const FieldDecl *getClassRefCntMember(const CXXRecordDecl *D) {
  for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end();
       field != e; ++field) {
    if (field->getName() == "mRefCnt") {
      return *field;
    }
  }
  return 0;
}

const FieldDecl *getBaseRefCntMember(QualType T);

const FieldDecl *getBaseRefCntMember(const CXXRecordDecl *D) {
  const FieldDecl *refCntMember = getClassRefCntMember(D);
  if (refCntMember && isClassRefCounted(D)) {
    return refCntMember;
  }

  for (CXXRecordDecl::base_class_const_iterator base = D->bases_begin(),
                                                e = D->bases_end();
       base != e; ++base) {
    refCntMember = getBaseRefCntMember(base->getType());
    if (refCntMember) {
      return refCntMember;
    }
  }
  return 0;
}

const FieldDecl *getBaseRefCntMember(QualType T) {
  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
    T = arrTy->getElementType();
  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
  return clazz ? getBaseRefCntMember(clazz) : 0;
}

bool typeHasVTable(QualType T) {
  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
    T = arrTy->getElementType();
  CXXRecordDecl *offender = T->getAsCXXRecordDecl();
  return offender && offender->hasDefinition() && offender->isDynamicClass();
}
}

namespace clang {
namespace ast_matchers {

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

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

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

/// 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 MozChecker::hasCustomAnnotation(&Node,
                                         "moz_no_addref_release_on_return");
}

/// 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 == and != binary operators.
AST_MATCHER(BinaryOperator, binaryEqualityOperator) {
  BinaryOperatorKind opcode = Node.getOpcode();
  return opcode == BO_EQ || opcode == BO_NE;
}

/// 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_MATCHER(BinaryOperator, isInSystemHeader) {
  return IsInSystemHeader(Finder->getASTContext(), Node);
}

/// This matcher will match locations in SkScalar.h.  This header contains a
/// known NaN-testing expression which we would like to whitelist.
AST_MATCHER(BinaryOperator, isInSkScalarDotH) {
  SourceLocation Loc = Node.getOperatorLoc();
  auto &SourceManager = Finder->getASTContext().getSourceManager();
  SmallString<1024> FileName = SourceManager.getFilename(Loc);
  return llvm::sys::path::rbegin(FileName)->equals("SkScalar.h");
}

/// 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 = Method->getName();
    return Name == "AddRef" || Name == "Release";
  }
  return false;
}

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

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

AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) {
  return MozChecker::hasCustomAnnotation(&Node, "moz_needs_no_vtable_type");
}

/// 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, needsMemMovable) {
  return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_type");
}

AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) {
  const CXXConstructorDecl *decl = Node.getCanonicalDecl();
  return
      // Skip ignored namespaces and paths
      !isInIgnoredNamespaceForImplicitCtor(decl) &&
      !isIgnoredPathForImplicitCtor(decl) &&
      // We only want Converting constructors
      decl->isConvertingConstructor(false) &&
      // We don't want copy of move constructors, as those are allowed to be
      // implicit
      !decl->isCopyOrMoveConstructor() &&
      // We don't want deleted constructors.
      !decl->isDeleted();
}

// We can't call this "isImplicit" since it clashes with an existing matcher in
// clang.
AST_MATCHER(CXXConstructorDecl, isMarkedImplicit) {
  return MozChecker::hasCustomAnnotation(&Node, "moz_implicit");
}

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

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

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

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

namespace {

void CustomTypeAnnotation::dumpAnnotationReason(DiagnosticsEngine &Diag,
                                                QualType T,
                                                SourceLocation Loc) {
  unsigned InheritsID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note,
      "%1 is a %0 type because it inherits from a %0 type %2");
  unsigned MemberID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "%1 is a %0 type because member %2 is a %0 type %3");
  unsigned ArrayID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note,
      "%1 is a %0 type because it is an array of %0 type %2");
  unsigned TemplID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note,
      "%1 is a %0 type because it has a template argument %0 type %2");

  AnnotationReason Reason = directAnnotationReason(T);
  for (;;) {
    switch (Reason.Kind) {
    case RK_ArrayElement:
      Diag.Report(Loc, ArrayID) << Pretty << T << Reason.Type;
      break;
    case RK_BaseClass: {
      const CXXRecordDecl *Decl = T->getAsCXXRecordDecl();
      assert(Decl && "This type should be a C++ class");

      Diag.Report(Decl->getLocation(), InheritsID) << Pretty << T
                                                   << Reason.Type;
      break;
    }
    case RK_Field:
      Diag.Report(Reason.Field->getLocation(), MemberID)
          << Pretty << T << Reason.Field << Reason.Type;
      break;
    case RK_TemplateInherited: {
      const CXXRecordDecl *Decl = T->getAsCXXRecordDecl();
      assert(Decl && "This type should be a C++ class");

      Diag.Report(Decl->getLocation(), TemplID) << Pretty << T << Reason.Type;
      break;
    }
    default:
      // FIXME (bug 1203263): note the original annotation.
      return;
    }

    T = Reason.Type;
    Reason = directAnnotationReason(T);
  }
}

bool CustomTypeAnnotation::hasLiteralAnnotation(QualType T) const {
#if CLANG_VERSION_FULL >= 306
  if (const TagDecl *D = T->getAsTagDecl()) {
#else
  if (const CXXRecordDecl *D = T->getAsCXXRecordDecl()) {
#endif
    return hasFakeAnnotation(D) || MozChecker::hasCustomAnnotation(D, Spelling);
  }
  return false;
}

CustomTypeAnnotation::AnnotationReason
CustomTypeAnnotation::directAnnotationReason(QualType T) {
  if (hasLiteralAnnotation(T)) {
    AnnotationReason Reason = {T, RK_Direct, nullptr};
    return Reason;
  }

  // Check if we have a cached answer
  void *Key = T.getAsOpaquePtr();
  ReasonCache::iterator Cached = Cache.find(T.getAsOpaquePtr());
  if (Cached != Cache.end()) {
    return Cached->second;
  }

  // Check if we have a type which we can recurse into
  if (const ArrayType *Array = T->getAsArrayTypeUnsafe()) {
    if (hasEffectiveAnnotation(Array->getElementType())) {
      AnnotationReason Reason = {Array->getElementType(), RK_ArrayElement,
                                 nullptr};
      Cache[Key] = Reason;
      return Reason;
    }
  }

  // Recurse into base classes
  if (const CXXRecordDecl *Decl = T->getAsCXXRecordDecl()) {
    if (Decl->hasDefinition()) {
      Decl = Decl->getDefinition();

      for (const CXXBaseSpecifier &Base : Decl->bases()) {
        if (hasEffectiveAnnotation(Base.getType())) {
          AnnotationReason Reason = {Base.getType(), RK_BaseClass, nullptr};
          Cache[Key] = Reason;
          return Reason;
        }
      }

      // Recurse into members
      for (const FieldDecl *Field : Decl->fields()) {
        if (hasEffectiveAnnotation(Field->getType())) {
          AnnotationReason Reason = {Field->getType(), RK_Field, Field};
          Cache[Key] = Reason;
          return Reason;
        }
      }

      // Recurse into template arguments if the annotation
      // MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS is present
      if (MozChecker::hasCustomAnnotation(
              Decl, "moz_inherit_type_annotations_from_template_args")) {
        const ClassTemplateSpecializationDecl *Spec =
            dyn_cast<ClassTemplateSpecializationDecl>(Decl);
        if (Spec) {
          const TemplateArgumentList &Args = Spec->getTemplateArgs();

          for (const TemplateArgument &Arg : Args.asArray()) {
            if (Arg.getKind() == TemplateArgument::Type) {
              QualType Type = Arg.getAsType();

              if (hasEffectiveAnnotation(Type)) {
                AnnotationReason Reason = {Type, RK_TemplateInherited, nullptr};
                Cache[Key] = Reason;
                return Reason;
              }
            }
          }
        }
      }
    }
  }

  AnnotationReason Reason = {QualType(), RK_None, nullptr};
  Cache[Key] = Reason;
  return Reason;
}

bool isPlacementNew(const CXXNewExpr *Expr) {
  // Regular new expressions aren't placement new
  if (Expr->getNumPlacementArgs() == 0)
    return false;
  const FunctionDecl *Decl = Expr->getOperatorNew();
  if (Decl && MozChecker::hasCustomAnnotation(Decl, "moz_heap_allocator")) {
    return false;
  }
  return true;
}

DiagnosticsMatcher::DiagnosticsMatcher() {
  astMatcher.addMatcher(varDecl().bind("node"), &scopeChecker);
  astMatcher.addMatcher(newExpr().bind("node"), &scopeChecker);
  astMatcher.addMatcher(materializeTemporaryExpr().bind("node"), &scopeChecker);
  astMatcher.addMatcher(
      callExpr(callee(functionDecl(heapAllocator()))).bind("node"),
      &scopeChecker);
  astMatcher.addMatcher(parmVarDecl().bind("parm_vardecl"), &scopeChecker);

  astMatcher.addMatcher(
      callExpr(allOf(hasDeclaration(noArithmeticExprInArgs()),
                     anyOf(hasDescendant(
                               binaryOperator(
                                   allOf(binaryArithmeticOperator(),
                                         hasLHS(hasDescendant(declRefExpr())),
                                         hasRHS(hasDescendant(declRefExpr()))))
                                   .bind("node")),
                           hasDescendant(
                               unaryOperator(
                                   allOf(unaryArithmeticOperator(),
                                         hasUnaryOperand(allOf(
                                             hasType(builtinType()),
                                             anyOf(hasDescendant(declRefExpr()),
                                                   declRefExpr())))))
                                   .bind("node")))))
          .bind("call"),
      &arithmeticArgChecker);
  astMatcher.addMatcher(
      constructExpr(
          allOf(hasDeclaration(noArithmeticExprInArgs()),
                anyOf(hasDescendant(
                          binaryOperator(
                              allOf(binaryArithmeticOperator(),
                                    hasLHS(hasDescendant(declRefExpr())),
                                    hasRHS(hasDescendant(declRefExpr()))))
                              .bind("node")),
                      hasDescendant(
                          unaryOperator(
                              allOf(unaryArithmeticOperator(),
                                    hasUnaryOperand(allOf(
                                        hasType(builtinType()),
                                        anyOf(hasDescendant(declRefExpr()),
                                              declRefExpr())))))
                              .bind("node")))))
          .bind("call"),
      &arithmeticArgChecker);

  astMatcher.addMatcher(recordDecl(hasTrivialCtorDtor()).bind("node"),
                        &trivialCtorDtorChecker);

  astMatcher.addMatcher(
      binaryOperator(
          allOf(binaryEqualityOperator(),
                hasLHS(has(
                    declRefExpr(hasType(qualType((isFloat())))).bind("lhs"))),
                hasRHS(has(
                    declRefExpr(hasType(qualType((isFloat())))).bind("rhs"))),
                unless(anyOf(isInSystemHeader(), isInSkScalarDotH()))))
          .bind("node"),
      &nanExprChecker);

  // First, look for direct parents of the MemberExpr.
  astMatcher.addMatcher(
      callExpr(
          callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")),
          hasParent(memberExpr(isAddRefOrRelease(), hasParent(callExpr()))
                        .bind("member")))
          .bind("node"),
      &noAddRefReleaseOnReturnChecker);
  // Then, look for MemberExpr that need to be casted to the right type using
  // an intermediary CastExpr before we get to the CallExpr.
  astMatcher.addMatcher(
      callExpr(
          callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")),
          hasParent(castExpr(
              hasParent(memberExpr(isAddRefOrRelease(), hasParent(callExpr()))
                            .bind("member")))))
          .bind("node"),
      &noAddRefReleaseOnReturnChecker);

  // Match declrefs with type "pointer to object of ref-counted type" inside a
  // lambda, where the declaration they reference is not inside the lambda.
  // This excludes arguments and local variables, leaving only captured
  // variables.
  astMatcher.addMatcher(lambdaExpr().bind("lambda"), &refCountedInsideLambdaChecker);

  // Older clang versions such as the ones used on the infra recognize these
  // conversions as 'operator _Bool', but newer clang versions recognize these
  // as 'operator bool'.
  astMatcher.addMatcher(
      methodDecl(anyOf(hasName("operator bool"), hasName("operator _Bool")))
          .bind("node"),
      &explicitOperatorBoolChecker);

  astMatcher.addMatcher(
      recordDecl(allOf(decl().bind("decl"), hasRefCntMember())),
      &noDuplicateRefCntMemberChecker);

  astMatcher.addMatcher(
      classTemplateSpecializationDecl(
          allOf(hasAnyTemplateArgument(refersToType(hasVTable())),
                hasNeedsNoVTableTypeAttr()))
          .bind("node"),
      &needsNoVTableTypeChecker);

  // Handle non-mem-movable template specializations
  astMatcher.addMatcher(
      classTemplateSpecializationDecl(
          allOf(needsMemMovable(),
                hasAnyTemplateArgument(refersToType(isNonMemMovable()))))
          .bind("specialization"),
      &nonMemMovableChecker);

  astMatcher.addMatcher(
      constructorDecl(isInterestingImplicitCtor(),
                      ofClass(allOf(isConcreteClass(), decl().bind("class"))),
                      unless(isMarkedImplicit()))
          .bind("ctor"),
      &explicitImplicitChecker);

  astMatcher.addMatcher(varDecl(hasType(autoNonAutoableType())).bind("node"),
                        &noAutoTypeChecker);

  astMatcher.addMatcher(constructorDecl(isExplicitMoveConstructor()).bind("node"),
                        &noExplicitMoveConstructorChecker);

  astMatcher.addMatcher(constructExpr(hasDeclaration(
                                          constructorDecl(
                                              isCompilerProvidedCopyConstructor(),
                                              ofClass(hasRefCntMember())))).bind("node"),
                        &refCountedCopyConstructorChecker);
}

// These enum variants determine whether an allocation has occured in the code.
enum AllocationVariety {
  AV_None,
  AV_Global,
  AV_Automatic,
  AV_Temporary,
  AV_Heap,
};

// XXX Currently the Decl* in the AutomaticTemporaryMap is unused, but it
// probably will be used at some point in the future, in order to produce better
// error messages.
typedef DenseMap<const MaterializeTemporaryExpr *, const Decl *> AutomaticTemporaryMap;
AutomaticTemporaryMap AutomaticTemporaries;

void DiagnosticsMatcher::ScopeChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();

  // There are a variety of different reasons why something could be allocated
  AllocationVariety Variety = AV_None;
  SourceLocation Loc;
  QualType T;

  if (const ParmVarDecl *D = Result.Nodes.getNodeAs<ParmVarDecl>("parm_vardecl")) {
    if (const Expr *Default = D->getDefaultArg()) {
      if (const MaterializeTemporaryExpr *E = dyn_cast<MaterializeTemporaryExpr>(Default)) {
        // We have just found a ParmVarDecl which has, as its default argument,
        // a MaterializeTemporaryExpr. We mark that MaterializeTemporaryExpr as
        // automatic, by adding it to the AutomaticTemporaryMap.
        // Reporting on this type will occur when the MaterializeTemporaryExpr
        // is matched against.
        AutomaticTemporaries[E] = D;
      }
    }
    return;
  }

  // Determine the type of allocation which we detected
  if (const VarDecl *D = Result.Nodes.getNodeAs<VarDecl>("node")) {
    if (D->hasGlobalStorage()) {
      Variety = AV_Global;
    } else {
      Variety = AV_Automatic;
    }
    T = D->getType();
    Loc = D->getLocStart();
  } else if (const CXXNewExpr *E = Result.Nodes.getNodeAs<CXXNewExpr>("node")) {
    // New allocates things on the heap.
    // We don't consider placement new to do anything, as it doesn't actually
    // allocate the storage, and thus gives us no useful information.
    if (!isPlacementNew(E)) {
      Variety = AV_Heap;
      T = E->getAllocatedType();
      Loc = E->getLocStart();
    }
  } else if (const MaterializeTemporaryExpr *E =
                 Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("node")) {
    // Temporaries can actually have varying storage durations, due to temporary
    // lifetime extension. We consider the allocation variety of this temporary
    // to be the same as the allocation variety of its lifetime.

    // XXX We maybe should mark these lifetimes as being due to a temporary
    // which has had its lifetime extended, to improve the error messages.
    switch (E->getStorageDuration()) {
    case SD_FullExpression:
      {
        // Check if this temporary is allocated as a default argument!
        // if it is, we want to pretend that it is automatic.
        AutomaticTemporaryMap::iterator AutomaticTemporary = AutomaticTemporaries.find(E);
        if (AutomaticTemporary != AutomaticTemporaries.end()) {
          Variety = AV_Automatic;
        } else {
          Variety = AV_Temporary;
        }
      }
      break;
    case SD_Automatic:
      Variety = AV_Automatic;
      break;
    case SD_Thread:
    case SD_Static:
      Variety = AV_Global;
      break;
    case SD_Dynamic:
      assert(false && "I don't think that this ever should occur...");
      Variety = AV_Heap;
      break;
    }
    T = E->getType().getUnqualifiedType();
    Loc = E->getLocStart();
  } else if (const CallExpr *E = Result.Nodes.getNodeAs<CallExpr>("node")) {
    T = E->getType()->getPointeeType();
    if (!T.isNull()) {
      // This will always allocate on the heap, as the heapAllocator() check
      // was made in the matcher
      Variety = AV_Heap;
      Loc = E->getLocStart();
    }
  }

  // Error messages for incorrect allocations.
  unsigned StackID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "variable of type %0 only valid on the stack");
  unsigned GlobalID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "variable of type %0 only valid as global");
  unsigned HeapID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "variable of type %0 only valid on the heap");
  unsigned NonHeapID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "variable of type %0 is not valid on the heap");
  unsigned NonTemporaryID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "variable of type %0 is not valid in a temporary");

  unsigned StackNoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note,
      "value incorrectly allocated in an automatic variable");
  unsigned GlobalNoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "value incorrectly allocated in a global variable");
  unsigned HeapNoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "value incorrectly allocated on the heap");
  unsigned TemporaryNoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "value incorrectly allocated in a temporary");

  // Report errors depending on the annotations on the input types.
  switch (Variety) {
  case AV_None:
    return;

  case AV_Global:
    StackClass.reportErrorIfPresent(Diag, T, Loc, StackID, GlobalNoteID);
    HeapClass.reportErrorIfPresent(Diag, T, Loc, HeapID, GlobalNoteID);
    break;

  case AV_Automatic:
    GlobalClass.reportErrorIfPresent(Diag, T, Loc, GlobalID, StackNoteID);
    HeapClass.reportErrorIfPresent(Diag, T, Loc, HeapID, StackNoteID);
    break;

  case AV_Temporary:
    GlobalClass.reportErrorIfPresent(Diag, T, Loc, GlobalID, TemporaryNoteID);
    HeapClass.reportErrorIfPresent(Diag, T, Loc, HeapID, TemporaryNoteID);
    NonTemporaryClass.reportErrorIfPresent(Diag, T, Loc,
                                           NonTemporaryID, TemporaryNoteID);
    break;

  case AV_Heap:
    GlobalClass.reportErrorIfPresent(Diag, T, Loc, GlobalID, HeapNoteID);
    StackClass.reportErrorIfPresent(Diag, T, Loc, StackID, HeapNoteID);
    NonHeapClass.reportErrorIfPresent(Diag, T, Loc, NonHeapID, HeapNoteID);
    break;
  }
}

void DiagnosticsMatcher::ArithmeticArgChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error,
      "cannot pass an arithmetic expression of built-in types to %0");
  const Expr *expr = Result.Nodes.getNodeAs<Expr>("node");
  if (const CallExpr *call = Result.Nodes.getNodeAs<CallExpr>("call")) {
    Diag.Report(expr->getLocStart(), errorID) << call->getDirectCallee();
  } else if (const CXXConstructExpr *ctr =
                 Result.Nodes.getNodeAs<CXXConstructExpr>("call")) {
    Diag.Report(expr->getLocStart(), errorID) << ctr->getConstructor();
  }
}

void DiagnosticsMatcher::TrivialCtorDtorChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error,
      "class %0 must have trivial constructors and destructors");
  const CXXRecordDecl *node = Result.Nodes.getNodeAs<CXXRecordDecl>("node");

  bool badCtor = !node->hasTrivialDefaultConstructor();
  bool badDtor = !node->hasTrivialDestructor();
  if (badCtor || badDtor)
    Diag.Report(node->getLocStart(), errorID) << node;
}

void DiagnosticsMatcher::NaNExprChecker::run(
    const MatchFinder::MatchResult &Result) {
  if (!Result.Context->getLangOpts().CPlusPlus) {
    // mozilla::IsNaN is not usable in C, so there is no point in issuing these
    // warnings.
    return;
  }

  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "comparing a floating point value to itself for "
                            "NaN checking can lead to incorrect results");
  unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "consider using mozilla::IsNaN instead");
  const BinaryOperator *expr = Result.Nodes.getNodeAs<BinaryOperator>("node");
  const DeclRefExpr *lhs = Result.Nodes.getNodeAs<DeclRefExpr>("lhs");
  const DeclRefExpr *rhs = Result.Nodes.getNodeAs<DeclRefExpr>("rhs");
  const ImplicitCastExpr *lhsExpr = dyn_cast<ImplicitCastExpr>(expr->getLHS());
  const ImplicitCastExpr *rhsExpr = dyn_cast<ImplicitCastExpr>(expr->getRHS());
  // The AST subtree that we are looking for will look like this:
  // -BinaryOperator ==/!=
  //  |-ImplicitCastExpr LValueToRValue
  //  | |-DeclRefExpr
  //  |-ImplicitCastExpr LValueToRValue
  //    |-DeclRefExpr
  // The check below ensures that we are dealing with the correct AST subtree
  // shape, and
  // also that both of the found DeclRefExpr's point to the same declaration.
  if (lhs->getFoundDecl() == rhs->getFoundDecl() && lhsExpr && rhsExpr &&
      std::distance(lhsExpr->child_begin(), lhsExpr->child_end()) == 1 &&
      std::distance(rhsExpr->child_begin(), rhsExpr->child_end()) == 1 &&
      *lhsExpr->child_begin() == lhs && *rhsExpr->child_begin() == rhs) {
    Diag.Report(expr->getLocStart(), errorID);
    Diag.Report(expr->getLocStart(), noteID);
  }
}

void DiagnosticsMatcher::NoAddRefReleaseOnReturnChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "%1 cannot be called on the return value of %0");
  const Stmt *node = Result.Nodes.getNodeAs<Stmt>("node");
  const FunctionDecl *func = Result.Nodes.getNodeAs<FunctionDecl>("func");
  const MemberExpr *member = Result.Nodes.getNodeAs<MemberExpr>("member");
  const CXXMethodDecl *method =
      dyn_cast<CXXMethodDecl>(member->getMemberDecl());

  Diag.Report(node->getLocStart(), errorID) << func << method;
}

void DiagnosticsMatcher::RefCountedInsideLambdaChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error,
      "Refcounted variable %0 of type %1 cannot be captured by a lambda");
  unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "Please consider using a smart pointer");
  const LambdaExpr *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");

  for (const LambdaCapture Capture : Lambda->captures()) {
    if (Capture.capturesVariable()) {
      QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType();

      if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
        Diag.Report(Capture.getLocation(), errorID)
          << Capture.getCapturedVar() << Pointee;
        Diag.Report(Capture.getLocation(), noteID);
      }
    }
  }
}

void DiagnosticsMatcher::ExplicitOperatorBoolChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "bad implicit conversion operator for %0");
  unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "consider adding the explicit keyword to %0");
  const CXXConversionDecl *method =
      Result.Nodes.getNodeAs<CXXConversionDecl>("node");
  const CXXRecordDecl *clazz = method->getParent();

  if (!method->isExplicitSpecified() &&
      !MozChecker::hasCustomAnnotation(method, "moz_implicit") &&
      !IsInSystemHeader(method->getASTContext(), *method) &&
      isInterestingDeclForImplicitConversion(method)) {
    Diag.Report(method->getLocStart(), errorID) << clazz;
    Diag.Report(method->getLocStart(), noteID) << "'operator bool'";
  }
}

void DiagnosticsMatcher::NoDuplicateRefCntMemberChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned warningID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error,
      "Refcounted record %0 has multiple mRefCnt members");
  unsigned note1ID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "Superclass %0 also has an mRefCnt member");
  unsigned note2ID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note,
      "Consider using the _INHERITED macros for AddRef and Release here");

  const CXXRecordDecl *decl = Result.Nodes.getNodeAs<CXXRecordDecl>("decl");
  const FieldDecl *refCntMember = getClassRefCntMember(decl);
  assert(refCntMember &&
         "The matcher checked to make sure we have a refCntMember");

  // Check every superclass for whether it has a base with a refcnt member, and
  // warn for those which do
  for (CXXRecordDecl::base_class_const_iterator base = decl->bases_begin(),
                                                e = decl->bases_end();
       base != e; ++base) {
    const FieldDecl *baseRefCntMember = getBaseRefCntMember(base->getType());
    if (baseRefCntMember) {
      Diag.Report(decl->getLocStart(), warningID) << decl;
      Diag.Report(baseRefCntMember->getLocStart(), note1ID)
          << baseRefCntMember->getParent();
      Diag.Report(refCntMember->getLocStart(), note2ID);
    }
  }
}

void DiagnosticsMatcher::NeedsNoVTableTypeChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error,
      "%0 cannot be instantiated because %1 has a VTable");
  unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "bad instantiation of %0 requested here");

  const ClassTemplateSpecializationDecl *specialization =
      Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("node");

  // Get the offending template argument
  QualType offender;
  const TemplateArgumentList &args =
      specialization->getTemplateInstantiationArgs();
  for (unsigned i = 0; i < args.size(); ++i) {
    offender = args[i].getAsType();
    if (typeHasVTable(offender)) {
      break;
    }
  }

  Diag.Report(specialization->getLocStart(), errorID) << specialization
                                                      << offender;
  Diag.Report(specialization->getPointOfInstantiation(), noteID)
      << specialization;
}

void DiagnosticsMatcher::NonMemMovableChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error,
      "Cannot instantiate %0 with non-memmovable template argument %1");
  unsigned note1ID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "instantiation of %0 requested here");

  // Get the specialization
  const ClassTemplateSpecializationDecl *specialization =
      Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("specialization");
  SourceLocation requestLoc = specialization->getPointOfInstantiation();

  // Report an error for every template argument which is non-memmovable
  const TemplateArgumentList &args =
      specialization->getTemplateInstantiationArgs();
  for (unsigned i = 0; i < args.size(); ++i) {
    QualType argType = args[i].getAsType();
    if (NonMemMovable.hasEffectiveAnnotation(args[i].getAsType())) {
      Diag.Report(specialization->getLocation(), errorID) << specialization
                                                          << argType;
      // XXX It would be really nice if we could get the instantiation stack
      // information
      // from Sema such that we could print a full template instantiation stack,
      // however,
      // it seems as though that information is thrown out by the time we get
      // here so we
      // can only report one level of template specialization (which in many
      // cases won't
      // be useful)
      Diag.Report(requestLoc, note1ID) << specialization;
      NonMemMovable.dumpAnnotationReason(Diag, argType, requestLoc);
    }
  }
}

void DiagnosticsMatcher::ExplicitImplicitChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "bad implicit conversion constructor for %0");
  unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note,
      "consider adding the explicit keyword to the constructor");

  // We've already checked everything in the matcher, so we just have to report
  // the error.

  const CXXConstructorDecl *Ctor =
      Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
  const CXXRecordDecl *Decl = Result.Nodes.getNodeAs<CXXRecordDecl>("class");

  Diag.Report(Ctor->getLocation(), ErrorID) << Decl->getDeclName();
  Diag.Report(Ctor->getLocation(), NoteID);
}

void DiagnosticsMatcher::NoAutoTypeChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "Cannot use auto to declare a variable of type %0");
  unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "Please write out this type explicitly");

  const VarDecl *D = Result.Nodes.getNodeAs<VarDecl>("node");

  Diag.Report(D->getLocation(), ErrorID) << D->getType();
  Diag.Report(D->getLocation(), NoteID);
}

void DiagnosticsMatcher::NoExplicitMoveConstructorChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "Move constructors may not be marked explicit");

  // Everything we needed to know was checked in the matcher - we just report
  // the error here
  const CXXConstructorDecl *D =
    Result.Nodes.getNodeAs<CXXConstructorDecl>("node");

  Diag.Report(D->getLocation(), ErrorID);
}

void DiagnosticsMatcher::RefCountedCopyConstructorChecker::run(
    const MatchFinder::MatchResult &Result) {
  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
  unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Error, "Invalid use of compiler-provided copy constructor "
                            "on refcounted type");
  unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
      DiagnosticIDs::Note, "The default copy constructor also copies the "
                           "default mRefCnt property, leading to reference "
                           "count imbalance issues. Please provide your own "
                           "copy constructor which only copies the fields which "
                           "need to be copied");

  // Everything we needed to know was checked in the matcher - we just report
  // the error here
  const CXXConstructExpr *E =
    Result.Nodes.getNodeAs<CXXConstructExpr>("node");

  Diag.Report(E->getLocation(), ErrorID);
  Diag.Report(E->getLocation(), NoteID);
}

class MozCheckAction : public PluginASTAction {
public:
  ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI,
                                   StringRef fileName) override {
#if CLANG_VERSION_FULL >= 306
    std::unique_ptr<MozChecker> checker(llvm::make_unique<MozChecker>(CI));
    ASTConsumerPtr other(checker->getOtherConsumer());

    std::vector<ASTConsumerPtr> consumers;
    consumers.push_back(std::move(checker));
    consumers.push_back(std::move(other));
    return llvm::make_unique<MultiplexConsumer>(std::move(consumers));
#else
    MozChecker *checker = new MozChecker(CI);

    ASTConsumer *consumers[] = {checker, checker->getOtherConsumer()};
    return new MultiplexConsumer(consumers);
#endif
  }

  bool ParseArgs(const CompilerInstance &CI,
                 const std::vector<std::string> &args) override {
    return true;
  }
};
}

static FrontendPluginRegistry::Add<MozCheckAction> X("moz-check",
                                                     "check moz action");
