// see License.txt for copyright and terms of use

#include "thrower_analyzer.h"        // this module
#include "thrower_cmd.h"    // ThrowerCmd
#include "thrower_global.h"
#include "oink.gr.gen.h"        // CCParse_Oink
#include "squash_util.h"

void RvAnalyzer::analyze(S_compound *s) {
  s->traverse(this->loweredVisitor);
}

bool RvAnalyzer::visitExpression(Expression *e) {
  bool b = ExpressionVisitor::visitExpression(e);
  if (b) expressionStack.push_back(e);
  return b;
}

void RvAnalyzer::postvisitExpression(Expression *e) {
  ExpressionVisitor::postvisitExpression(e);
  std::list<Expression*>::iterator it = expressionStack.end();
  --it;
  xassert(*it == e);
  expressionStack.erase(it);
}

bool RvAnalyzer::visitStatement(Statement *s) {
  bool b = ExpressionVisitor::visitStatement(s);
  if (b) statementStack.push_back(s);
  return b;
}

void RvAnalyzer::postvisitStatement(Statement *s) {
  std::list<Statement*>::iterator it = statementStack.end();
  --it;
  xassert(*it == s);
  statementStack.erase(it);
  ExpressionVisitor::postvisitStatement(s);
}

void RvAnalyzer::processAssign(Expression *arg, Usage &usage, Statement *sOuter) {
  if (arg->isE_funCall()) {
    // yank fcall out of nsresult.operator==(fcall())
    usage.nsassignFuncs.push_back(FcallBlock(arg->asE_funCall(), sOuter));
  } else {
    // ok not fcall..must be a var or err-const assignment.what else would it be
    E_variable *v = arg->ifE_variable();
    xassert(v);
    rvmap::iterator vit = nsresultVars.find(v->var);
    // if assigning another nsresult...make a record
    if (vit != nsresultVars.end()) {
      vit->second.valueUsed.push_back(v);
    }
    usage.nsassignVars.push_back(AssignStmt(v, sOuter));
  }
}

bool RvAnalyzer::visitS_decl(S_decl *s) {
  Declaration *decl = s->decl;
  if (!is_nsresult(decl->spec)) return true;
  xassert(!decl->decllist->isEmpty()
          && decl->decllist->butFirst()->isEmpty());

  Declarator *d = decl->decllist->first();
  try {
    Expression *arg = 
      SOME(d->ctorStatement->asS_expr()->expr->expr
           ->asE_constructor()->args)->first()->expr;
    // STL-magically a new entry is added to the map
    processAssign(arg, nsresultVars[d->var], s);
  } catch (x_match&) {
    nsresultVars[d->var];
  }
  return false;
}

bool RvAnalyzer::is_nsresult(TypeSpecifier *ts) {
  TS_name *tsname = ts->ifTS_name();
  if (!tsname) return false;
  
  PQ_name *pqname = tsname->name->ifPQ_name();
  return (pqname && pqname->name == str_nsresult);
}

bool RvAnalyzer::visitE_variable(E_variable *e) {
  rvmap::iterator it = nsresultVars.find(e->var);
  if (it == nsresultVars.end()) return true;
  
  Usage &usage = it->second;
  Statement *s = statementStack.back();
  
  std::list<Expression*>::reverse_iterator ie = expressionStack.rbegin();
  // untroll through constructor redundancy
  Expression *eOuter = NULL;
  while (true) {
    if (ie == expressionStack.rend()) {
      eOuter = NULL;
      break;
    } 
    eOuter = *ie;
    ++ie;
    switch (eOuter->kind()) {
    case Expression::E_CONSTRUCTOR:
      continue;
    case Expression::E_BINARY:
      {
        E_binary *eb = eOuter->asE_binary();
        if (eb->op == BIN_COMMA && eb->e1->isE_constructor()) {
          continue;
        }
      }
    default:
      break;
    }
    break;
  }
  try {  
    if (!eOuter) {
      if (s->isS_expr()) {
        S_return *ret = statementPeek(1)->asS_return();
        // if we are returning an rv that was never assigned by
        // calling a func, signal that it needs to be wrapped
        if (usage.nsassignFuncs.empty()) {
          returns.push_back(ReturnWrapped(ret, true));
          processedStatements.insert(ret);
          return false;
        }
        //guardFor is the functioncall that we are checking the error for
        FcallBlock &returnFor = usage.nsassignFuncs.back();
        returnFor.rets.push_back(ret);
 
      }
      return false;
    }

    switch (eOuter->kind()) {
    case Expression::E_FUNCALL:
      {
        if (!usage.valueUsed.empty()) {
          if (e == usage.valueUsed.back()) {
            //already processed in in e_fieldacc below
            return false;
          }
        }
        E_funCall *f = eOuter->asE_funCall();
        StringRef name = f->func->asE_variable()->name
          ->asPQ_name()->name;

        xassert(!usage.nsassignFuncs.empty());
        //guardFor is the functioncall that we are checking the error for
        FcallBlock &guardFor = usage.nsassignFuncs.back();
        S_if *sif = s->asS_if();
        if (name == str_NS_SUCCEEDED) {
          guardFor.nssucceeded.push_back(FcallBlock::FcallStmt(f, sif));
        } else if (name == str_NS_FAILED) {
          try {
            // now detect if NS_FAILED(rv) -> return rv 
            S_compound *thenBranch = sif->thenBranch->asS_compound();
            S_return *ret = thenBranch->stmts.first()->asS_return();
            E_variable *v = getCtorArg(ret->ctorStatement)->asE_variable();
            xmatch(e->var == v->var
                   // make sure there is nothing else in this if clause
                   && sif->elseBranch->asS_compound()->stmts.first()->isS_skip()
                   && thenBranch->stmts.count() == 1);
            // now remove nsfailed block from prior analysis results
            automaticErrors.push_back(sif);
            processedStatements.insert(ret);
            // should i also add this return to the returns?
          } catch (x_match&) {
            guardFor.nsfailed.push_back(FcallBlock::FcallStmt(f, sif));
          }
        } 
        return false;
      }
    case Expression::E_FIELDACC:
      {
        xmatch(eOuter->asE_fieldAcc()->fieldName->asPQ_operator()
               ->o->asON_operator()->op == OP_ASSIGN
               && ie != expressionStack.rend());
        // get the expr outside of E_fieldAcc
        Expression *arg = (*ie)->asE_funCall()->args->first()->expr;
        processAssign(arg, usage, s);
        processedStatements.insert(s);
        return false;
      }
    default:
      break;
    }
  } catch (x_match&) {
  }

  cerr << toString(e->loc) << ": Unrecognized outer expression '" 
       << eOuter->kindName() << "' within: " << s->kindName() << endl;
    
  return false;
}

RvAnalyzer::Usage *RvAnalyzer::getNsResult(Variable *v) {
  rvmap::iterator it = nsresultVars.find(v);
  if (it == nsresultVars.end()) return NULL;
  return &it->second;
}

bool RvAnalyzer::is_nsresultFcall(Expression *e) {
  Type *t = skipPtrOrRef(e->type);
  if (t->isCVAtomicType()) {
    AtomicType *at = t->asCVAtomicType()->atomic;
    return at->isCompoundType()
      && str_nsresult == at->asCompoundType()->name
      && e->isE_funCall();
  }
  return false;
}

inline StringRef getVarName(Expression *v) 
  throw (x_match) 
{
  return v->asE_variable()->name->asPQ_name()->name;
}

void RvAnalyzer::postvisitS_return(S_return *s) {
  if (!in_nsresult
      || setContains(processedStatements, (Statement*)s)) return;
      
  bool isConst = false;
  try {
    Expression *e = getCtorArg(s->ctorStatement);
    // allow returning nsresulty functions as they will become void
    if (is_nsresultFcall(e)) return;
    E_variable *v = e->asE_variable();
    isConst = skipPtrOrRef(v->type)->isConst();
    StringRef name = getVarName(v);
    //note this does force a pattern match on PQ_name
    if (name == str_NS_OK) {
      Statement *snuke =
        s == statementStack.front()->asS_compound()->stmts.last() ? s : NULL;
      returnNS_OKs.push_back(NS_OKinfo(v, snuke));
      return;
    }
    // handle if(!foo) return NS_ERROR_OUT_OF_MEMORY
    static const StringRef str_NS_ERROR_OUT_OF_MEMORY = 
      globalStrTable("NS_ERROR_OUT_OF_MEMORY");
    xmatch(isConst && name == str_NS_ERROR_OUT_OF_MEMORY);
    S_if *sif = statementPeek(1)->asS_if();
    E_unary *eNot = sif->cond->asCN_expr()->expr->expr->asE_unary();
    xmatch(eNot->op == UNY_NOT);
    Expression *ptr = eNot->expr;
    while (ptr->isE_deref()) {
      ptr = ptr->asE_deref()->ptr;
    }
    xmatch(ptr->isE_variable() || ptr->isE_fieldAcc());
    Type *t = ptr->type;
    while (t->isReferenceType()) t = t->getAtType();
    
    // make sure the return is sole statement
    xmatch(statementPeek()->asS_compound()->stmts.count() == 1);
    automaticErrors.push_back(sif);
    return;
   } catch (x_match&) {

  }
  returns.push_back(ReturnWrapped(s, !isConst));
}

void RvAnalyzer::postvisitE_funCall(E_funCall *e) {
  Statement *s = statementStack.back();
  if(is_nsresultFcall(e)
     && expressionStack.size() == 1
     && s->isS_expr()
     && !setContains(processedStatements, s)) {
    ignoredErrors.push_back(e);
  }
}

void RvAnalyzer::backtrace() {
  cerr << toString(statementStack.back()->loc) << " Backtrace:" << endl;
  for(std::list<Expression*>::reverse_iterator it = expressionStack.rbegin();
      it != expressionStack.rend();
      ++it) {
    cerr << " " << (*it)->kindName() << endl;
  }
  for(std::list<Statement*>::reverse_iterator it = statementStack.rbegin();
      it != statementStack.rend();
      ++it) {
    cerr << " " << (*it)->kindName() << endl;
  }
}

Statement* RvAnalyzer::statementPeek(unsigned int offset) throw (x_match) {
  std::list<Statement*>::reverse_iterator it = statementStack.rbegin();
  while (offset) {
    xmatch(++it != statementStack.rend());
    offset--;
  }
  xmatch(it != statementStack.rend());
  return *it;
}
