/* 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 "ScopeChecker.h"
#include "CustomMatchers.h"

void ScopeChecker::registerMatchers(MatchFinder *AstMatcher) {
  AstMatcher->addMatcher(varDecl().bind("node"), this);
  AstMatcher->addMatcher(cxxNewExpr().bind("node"), this);
  AstMatcher->addMatcher(
      materializeTemporaryExpr(
          unless(hasDescendant(cxxConstructExpr(allowsTemporary()))))
          .bind("node"),
      this);
  AstMatcher->addMatcher(
      callExpr(callee(functionDecl(heapAllocator()))).bind("node"), this);
}

// 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 ScopeChecker::check(const MatchFinder::MatchResult &Result) {
  // There are a variety of different reasons why something could be allocated
  AllocationVariety Variety = AV_None;
  SourceLocation Loc;
  QualType T;
  bool IsStaticLocal = false;

  if (const ParmVarDecl *D = Result.Nodes.getNodeAs<ParmVarDecl>("node")) {
    if (D->hasUnparsedDefaultArg() || D->hasUninstantiatedDefaultArg()) {
      return;
    }
    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->getBeginLoc();
    IsStaticLocal = D->isStaticLocal();
  } 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->getBeginLoc();
    }
  } 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->getBeginLoc();
  } 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->getBeginLoc();
    }
  }

  // Error messages for incorrect allocations.
  const char *Stack = "variable of type %0 only valid on the stack";
  const char *Global = "variable of type %0 only valid as global";
  const char *Heap = "variable of type %0 only valid on the heap";
  const char *NonHeap = "variable of type %0 is not valid on the heap";
  const char *NonTemporary = "variable of type %0 is not valid in a temporary";
  const char *Temporary = "variable of type %0 is only valid as a temporary";
  const char *StaticLocal = "variable of type %0 is only valid as a static "
                            "local";

  const char *StackNote =
      "value incorrectly allocated in an automatic variable";
  const char *GlobalNote = "value incorrectly allocated in a global variable";
  const char *HeapNote = "value incorrectly allocated on the heap";
  const char *TemporaryNote = "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(*this, T, Loc, Stack, GlobalNote);
    HeapClass.reportErrorIfPresent(*this, T, Loc, Heap, GlobalNote);
    TemporaryClass.reportErrorIfPresent(*this, T, Loc, Temporary, GlobalNote);
    if (!IsStaticLocal) {
      StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal,
                                            GlobalNote);
    }
    break;

  case AV_Automatic:
    GlobalClass.reportErrorIfPresent(*this, T, Loc, Global, StackNote);
    HeapClass.reportErrorIfPresent(*this, T, Loc, Heap, StackNote);
    TemporaryClass.reportErrorIfPresent(*this, T, Loc, Temporary, StackNote);
    StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal,
                                          StackNote);
    break;

  case AV_Temporary:
    GlobalClass.reportErrorIfPresent(*this, T, Loc, Global, TemporaryNote);
    HeapClass.reportErrorIfPresent(*this, T, Loc, Heap, TemporaryNote);
    NonTemporaryClass.reportErrorIfPresent(*this, T, Loc, NonTemporary,
                                           TemporaryNote);
    StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal,
                                          TemporaryNote);
    break;

  case AV_Heap:
    GlobalClass.reportErrorIfPresent(*this, T, Loc, Global, HeapNote);
    StackClass.reportErrorIfPresent(*this, T, Loc, Stack, HeapNote);
    NonHeapClass.reportErrorIfPresent(*this, T, Loc, NonHeap, HeapNote);
    TemporaryClass.reportErrorIfPresent(*this, T, Loc, Temporary, HeapNote);
    StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal, HeapNote);
    break;
  }
}
