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

#include "outparamdel.h"        // this module
#include "outparamdel_cmd.h"    // OutparamdelCmd
#include "outparamdel_global.h"
#include "oink.gr.gen.h"        // CCParse_Oink
#include "strutil.h"            // quoted
#include "oink_util.h"
#include "expr_visitor.h"
#include "squash_util.h"
#include "patcher.h"
#include "outparamdel_cmd.h"
#include "cppundolog.h"
#include <fstream>
#include <list>
#include <ctype.h>
#include <sstream>

typedef std::pair<std::string, std::string> str_pair;
struct OutParamInfo;
typedef std::map<Variable*, OutParamInfo*> outparam_map;

struct OutParamInfo {
  OutParamInfo(): isVirtual(false), already_AddRefed(false) {
  }
  std::string toString() const {
    std::stringstream ss;
    ss << className << "::" << memberName << '(';
    for(std::vector<std::string>::const_iterator it = paramTypes.begin();
        it != paramTypes.end();
        it++) {
      if (it != paramTypes.begin())
        ss << ',';
      ss << *it;
    }
    ss << ")," << param << ',';
    if (already_AddRefed)
      ss << "already_AddRefed";
    return ss.str();
  }
  StringRef className;
  std::string memberName;
  // index of the param to remove
  unsigned int param;
  // name used in getter
  std::string outparamName;
  bool isVirtual;
  std::vector<std::string> paramTypes;
  bool already_AddRefed;
};

typedef std::list<OutParamInfo> outparam_list;

class OutparamDel : public ExpressionVisitor {
public:
  OutparamDel(Patcher &patcher, OutparamdelCmd const &cmd): 
    patcher(patcher),
    cmd(cmd),
    currentOpi(NULL),
    outmostExpression(NULL),
    outmostStatement(NULL)
  {
    readParams(outparams, cmd.outparamFile);
  }
  virtual bool visitTypeSpecifier(TypeSpecifier *ts);
  virtual bool visitFunction(Function *f);
  virtual void postvisitFunction(Function *f);
  virtual void postvisitS_return(S_return *e);
  virtual void postvisitE_funCall(E_funCall *e);
  virtual bool visitExpression(Expression *e);
  virtual void postvisitExpression(Expression *e);
  virtual bool visitStatement(Statement *s);
  virtual bool visitS_if(S_if *s);
  virtual bool visitS_while(S_while *s);
  virtual bool visitS_for(S_for *s);
  virtual bool visitS_doWhile(S_doWhile *s);
  virtual void postvisitStatement(Statement *s);

private:
  void readParams(outparam_list &outparams, std::string const &file);
  std::string getOutparamWithCast(Expression *inExpr, 
                                  std::string &strGetterAddrefsCast,
                                  OutParamInfo const &opi);
  void visitMR_decl(MR_decl *m);
  OutParamInfo* isMatch(D_func *df, 
			Variable *member,
			ASTTypeId *&outparam,
			Type *thisType);
  // check callsites
  OutParamInfo* isMatchingCall(E_funCall *e);
  str_pair* rewriteFunctionDeclaration(OutParamInfo const &opi,
				      unsigned int paramCount,
				      CPPSourceLoc cslFunctionNameStart,
				      CPPSourceLoc cslStart,
				      CPPSourceLoc cslParamStart,
				      //  using middle,because don't have endloc for that
				      CPPSourceLoc cslParamMiddle,
				      CPPSourceLoc cslEnd);


private:
  outparam_list outparams;
  Patcher &patcher;
  OutparamdelCmd const &cmd;
  // this is empty unless inside of a body of func that's being rewritten
  OutParamInfo *currentOpi;
  outparam_map mapOutparams;
  // used to trigger rewrites for things that act upon ret vals
  Expression *outmostExpression;
  Statement *outmostStatement;
};

void OutparamDel::readParams(outparam_list &outparams, std::string const &file) {
  std::ifstream f(file.c_str());
  if (!f.is_open()) {
    cerr << "Couldn't open configuration file '"<<file<<"'" << endl;
    exit(1);
  }

  std::string line;
  unsigned int lineNum = 0;
  while (std::getline(f, line)) {
    lineNum++;
    OutParamInfo opi;
    std::string::size_type old_n, n;

    if (line.empty() || line[0] == '#') continue;

    // skip dehydra location info
    n = line.find(" ");
    if (n != std::string::npos) line.erase(0, n + 1);

    n = line.find("::");
    if(n == std::string::npos)
      goto error;
    opi.className = globalStrTable(line.substr(0, n).c_str());
    n = line.find('(', old_n = n + 2);
    if(n == std::string::npos)
      goto error;
    opi.memberName = line.substr(old_n, n - old_n);
    n = line.find(')', old_n = n + 1);
    if(n == std::string::npos)
      goto error;
    std::string paramTypes = line.substr(old_n, n -old_n);
    std::string::size_type n2 = paramTypes.find(',');
    std::string::size_type old_n2 = 0;
    while(old_n2 < paramTypes.size()) {
      if (n2 == std::string::npos) n2 = paramTypes.size();
      opi.paramTypes.push_back(paramTypes.substr(old_n2, n2 - old_n2));
      old_n2 = n2 + 1;
      n2 = paramTypes.find(',', old_n2);
    }
    std::string param = line.substr(n + 2);
    opi.param = atoi(param.c_str());
    if (cmd.debug) {
      cerr << opi.className << "::"<<opi.memberName
           << "(" << opi.paramTypes.size() << ")"
           << "," << opi.param << endl;
    }
    outparams.push_back(opi);
  }
  f.close();
  return;
 error:
  f.close();
  cerr << file << ":" << lineNum << ": Parse error, invalid input format:"
       << line << endl;
  exit(1);
}

void Outparamdel::process(OutparamdelCmd const &cmd) {
  Patcher patcher;
  OutparamDel od(patcher, cmd);

  foreachSourceFile {
    File *file = files.data();
    patcher.setFile(file->name);
    
    TranslationUnit *unit = file2unit.get(file);
    unit->traverse(od.loweredVisitor);
  }
}

bool OutparamDel::visitTypeSpecifier(TypeSpecifier *ts) {
  TS_classSpec *sp = ts->ifTS_classSpec();
  if (!sp) return true;
  for (ASTListIterNC<Member> i(sp->members->list);
       ! i.isDone();
       i.adv()) {
    MR_decl *d = i.data()->ifMR_decl();
    if (d) visitMR_decl(d);
  }
  return true;
}

void OutparamDel::visitMR_decl(MR_decl *m) {
  Declaration *dn = m->d;
  Declarator *d = dn->decllist->first();
  if (!d) return;
  D_func *df = d->decl->ifD_func();

  Variable *v = d->var;
  // these are from the type system
  if (!d->type->isFunctionType()) return;
  
  FunctionType *f = d->type->asFunctionType();
  SObjListIter<Variable> typedParams(f->params);
  ASTTypeId *outparam = NULL;
  Type *thisType = NULL;
  if (!typedParams.isDone()) thisType = typedParams.data()->type;
  OutParamInfo *opi = isMatch(df, v, outparam, thisType);
  if(opi && outparam) {
    mapOutparams[v] = opi;
    if (!opi->isVirtual) {
      opi->isVirtual = dn->dflags & DF_VIRTUAL;
    }
    rewriteFunctionDeclaration(*opi, f->params.count() - 1,
			       dn->spec->asTS_name()->name->loc,
			       dn->decllist->first()->decl->loc,
			       outparam->spec->loc,
			       outparam->decl->decl->loc,
			       m->endloc);
  }
}

// Based on a func with the same name in squash_expr_rewriter.cc
static bool matchesType(StringRef type, Type *t, bool checkAncestors) {
  static StringRef strStar = globalStrTable("*");
  if (type == strStar) return true;
  if (t->isPtrOrRef() || t->isArrayType()) {
    return matchesType(type, t->getAtType(), checkAncestors);
  }
  if (!t->isCompoundType()) {
    return false;
  }
  CompoundType *ct = t->asCompoundType();
  TS_classSpec *c = ct->syntax;
  if (type == c->name->getName())
    return true;
  
  if (!checkAncestors)
    return false;
  for (FakeList<BaseClassSpec > *bs = c->bases;
       !bs->isEmpty();
       bs = bs->butFirst()) {
    BaseClassSpec *base = bs->first();
    if(matchesType(type, base->type->selfType, true))
      return true;
  }
  return false;
}

// Check function declaration if it is a rewrite candidate
OutParamInfo* OutparamDel::isMatch(D_func *df, 
				   Variable *member,
				   ASTTypeId *&outparam,
				   Type *thisType) {
  for (outparam_list::iterator i = outparams.begin();
       i != outparams.end();
       ++i) {
    if (member->name != i->memberName) continue;
    
    // type of "this" parameter is the same as the class name
    if (! matchesType(i->className, thisType, i->isVirtual)) {
      continue; 
    }
    unsigned int param = i->param;
    for (FakeList<ASTTypeId>* params = df->params;
	 ! params->isEmpty();
	 params = params->butFirst()) {
      ASTTypeId *p = params->first();
      if (param--) continue;
      outparam = p;
      break;
    } 
    if (!outparam) continue;

    // now check if this is the correct overload
    FunctionType *t = skipPtrOrRef(member->type)->asFunctionType();

    SObjListIter<Variable> params(t->params);
    if (t->isMethod()) params.adv();

    for (param = 0; 
         !params.isDone() && param < i->paramTypes.size();
         params.adv(), param++) {
      Type *t = params.data()->type;
      string typeName = t->toString();
      if (i->paramTypes[param] != typeName.c_str()) {
        if (cmd.debug) {
          cerr << "'" << i->paramTypes[param] << "'" << " != " 
               << "'" << typeName << "'" << endl;
        }
        break;
      }
    }

    // if param types don't match, move on
    if (param < i->paramTypes.size()) {
      if (cmd.debug) {
        cerr << i->className << "::" << i->memberName
             << " doesn't match param types" << endl;
      }
      continue;
    }
    return &(*i);
  }
  return NULL;
}

str_pair* OutparamDel::rewriteFunctionDeclaration(OutParamInfo const &opi,
					     unsigned int paramCount,
					     CPPSourceLoc cslStart,
					     CPPSourceLoc cslFunctionNameStart,
					     CPPSourceLoc cslParamType,
					     //  using middle,because don't have endloc for that
					     CPPSourceLoc cslParamName,
					     CPPSourceLoc cslEnd) {
  CPPSourceLoc *validateLocs[] = {&cslStart, &cslFunctionNameStart, 
				&cslParamType, &cslParamName, &cslEnd};
  // this is non-NULL if method is decorated with NS_IMETHOD[IMP]
  MacroUndoEntry *macroNS_IMETHOD = NULL;
  for (unsigned int i = 0; 
       i < sizeof(validateLocs)/sizeof(validateLocs[0]);
       i++) {
    if (validateLocs[i]->hasExactPosition()) continue;
    if (validateLocs[i] == &cslStart) {
      macroNS_IMETHOD = cslStart.macroExpansion;
      while (macroNS_IMETHOD->parent) macroNS_IMETHOD = macroNS_IMETHOD->parent;

      continue;
    }
    cerr << toString(validateLocs[i]->loc()) << ": " << opi.memberName
	 <<" declared within " 
	 << validateLocs[i]->macroExpansion->name << endl;;
    return NULL;
  }
  // first strip the outparam
  PairLoc pairMemberToOutparam(cslFunctionNameStart, cslParamType);
  PairLoc pairOutparamToMember(cslParamName, cslEnd);

  std::string strMemberToOutparam = patcher.getRange(pairMemberToOutparam);
  std::string::size_type pos = strMemberToOutparam.find_last_of("(,");
  xassert(pos != std::string::npos);

  // Erase text prior to outparam.
  // don't erase '(', but do erase ',' and everything else
  if (strMemberToOutparam[pos] == '(') {
    pos++;
  }
  strMemberToOutparam.erase(pos);
  std::string strOutparamToMember = patcher.getRange(pairOutparamToMember);
  pos = strOutparamToMember.find_first_of(",)");
  xassert(pos != std::string::npos);
  
  // if outparam is the first paramter out of many, drop the comma after it
  if (paramCount > 1 && opi.param == 0) {
    do {
      pos++;
      // also skip whitespace after comma
    } while (pos < strOutparamToMember.size() && isspace(strOutparamToMember[pos]));
  }
  strOutparamToMember.erase(0,pos);
  std::string strFunc = strMemberToOutparam + strOutparamToMember;

  // now figure out type & name of outparam
  std::string strOutparamType = patcher.getRange(PairLoc(cslParamType, cslParamName));
  std::string strOutparamName = patcher.getRange(PairLoc(cslParamName, cslEnd));
  
  pos = strOutparamName.find_first_of(",)");
  xassert(pos != std::string::npos);
  // get rid of trailing spaces
  while(pos && isspace(strOutparamName[pos - 1])) pos--;
  strOutparamName.erase(pos);
  xassert(pos != std::string::npos);
  pos = strOutparamName.find_last_of("*&");
  xassert(pos != std::string::npos);
  strOutparamType += strOutparamName.substr(0,pos);
  pos = strOutparamName.find_last_of(" &*");
  xassert(pos != std::string::npos);
  strOutparamName.erase(0, pos+1);
  
  if (!cslStart.hasExactPosition()) cslStart.overrideLoc(cslStart.loc());
  std::string strFuncType = patcher.getRange(PairLoc(cslStart, cslFunctionNameStart));
  pos = strFuncType.size() - 1;
  while (isspace(strFuncType[pos])) {
    pos--;
  }
  
  // preserve whitespace after the type
  patcher.printPatch( ( macroNS_IMETHOD ?
			macroNS_IMETHOD->name +("_(" + strOutparamType) + ")" 
			: strOutparamType )
		     + strFuncType.substr(pos + 1) + strFunc,
		     PairLoc(cslStart, cslEnd));

  return new str_pair(strOutparamType, strOutparamName);
}

bool OutparamDel::visitFunction(Function *f) {
  PQName  *pqname = f->nameAndParams->getDeclaratorId();
  PQ_qualifier *q = pqname->ifPQ_qualifier();
  if (!q) return true;
  PQ_name *pq = q->rest->ifPQ_name();
  if (!pq) return true;
  D_func *df = f->nameAndParams->decl->ifD_func();
  ASTTypeId *outparam = NULL;

  FunctionType *funcType = f->funcType;
  if (!funcType->isMethod()) return true;

  OutParamInfo *opi = isMatch(df, f->nameAndParams->var, outparam, 
			      funcType->getClassOfMember()->selfType);
  currentOpi = opi;
  if (!opi) return true;

  SourceLoc bodyLoc = SL_UNKNOWN;
  {
    StringRef file;
    int line;
    int col;
    sourceLocManager->decodeLineCol(f->body->loc, file, line, col);
    bodyLoc = sourceLocManager->encodeLineCol(file, line, col + 1);
  }
  CPPSourceLoc cslType(f->retspec->loc);
  CPPSourceLoc endcslType(f->nameAndParams->decl->loc);
  CPPSourceLoc cslOutparam(outparam->spec->loc);
  CPPSourceLoc cslOutparamName(outparam->decl->decl->loc);
  CPPSourceLoc endcsl(bodyLoc);
  
  std::auto_ptr<str_pair> type_name( 
    rewriteFunctionDeclaration(*opi, 
			       f->nameAndParams->type->asFunctionType()->params.count() - 1, 
			       cslType,
			       endcslType,
			       cslOutparam,
			       cslOutparamName,	      
			       endcsl));
  if (!type_name.get())
    return true;
  opi->outparamName = type_name->second;
  // don't bother rewriting more if the func has no body
  
  ASTList<Statement> &ls = f->body->asS_compound()->stmts;
  if (ls.isEmpty()) return true;
  
  StringRef file;
  int line;
  int col;
  CPPSourceLoc cslIndent(ls.first()->loc);
  // we don't care if cslIndent points within a macro
  cslIndent.overrideLoc(cslIndent.loc());
  // Don't need an exact position if we are just
  // incerting stuff infront
  // xassert(cslIndent.hasExactPosition());
  sourceLocManager->decodeLineCol(cslIndent.loc(), file, line, col);

  std::string strIndent = patcher.getLine(line, file).substr(0, col - 1);
  std::stringstream ss;
  ss <<  type_name->first << " __" << type_name->second << " = 0;";
  ss << "\n" << strIndent <<type_name->first << "* " << type_name->second << " = &__" << type_name->second << ";";

  static StringRef strEnsureArgPointer = globalStrTable("NS_ENSURE_ARG_POINTER");
  if (cslIndent.macroExpansion
      && cslIndent.macroExpansion->name == strEnsureArgPointer) {
    // if the first part of func is a pesky NS_ENSURE_ARG_POINTER
    // replace it altogether instead of prepending stuff
    CPPSourceLoc endloc = cslIndent;
    endloc.overrideLoc(cslIndent.macroExpansion->preEndLoc);
    patcher.printPatch(ss.str(), PairLoc(cslIndent, endloc));
  } else {
    ss << "\n" << strIndent;
    patcher.insertBefore(cslIndent, ss.str());
  }
  return true;
}

void OutparamDel::postvisitFunction(Function *f) {
  currentOpi = NULL;
}

void OutparamDel::postvisitS_return(S_return *s) {
  if (!currentOpi) return;

  Expression *e = s->expr->expr;
  CPPSourceLoc csl(e->loc);
  CPPSourceLoc endcsl(e->endloc);
  CPPSourceLoc cslReturn(s->loc);
  bool lostInMacro = !(csl.hasExactPosition() && endcsl.hasExactPosition());
  if (!cslReturn.hasExactPosition() && lostInMacro) {
    static StringRef strEnsureTrue = globalStrTable("NS_ENSURE_TRUE");
    if (csl.macroExpansion == endcsl.macroExpansion
	&& csl.macroExpansion->name == strEnsureTrue) {
      // the macros have the original position
      // no need to recalculate
      csl.overrideLoc(csl.loc());
      endcsl = csl;
      endcsl.overrideLoc(csl.macroExpansion->preEndLoc);
      PairLoc pairMacro(csl, endcsl);
      std::string strMacro = patcher.getRange(pairMacro);
      
      std::string::size_type pos = strMacro.rfind(',');
      xassert(pos != std::string::npos);
      while(isspace(strMacro[pos + 1])) pos++;

      std::string::size_type endpos = strMacro.find(')', pos + 1);
      patcher.printPatch(strMacro.substr(0, pos + 1)
			 + "nsnull"
			 + strMacro.substr(endpos), pairMacro);
      return;
    } 
    CPPSourceLoc &bad = !csl.hasExactPosition() ? csl : endcsl;
    cerr << toString(bad.loc())
	 << ": getter's return statement is within an unsupported macro: " 
	 << csl.macroExpansion->name << endl;
    return;
  } else if (cslReturn.hasExactPosition() && lostInMacro) {
    MacroUndoEntry *macro = (csl.macroExpansion ? csl : endcsl).macroExpansion;
    cerr << toString(cslReturn.loc())
	 << ": getter's return expression within a non-obvious macro. Guessing that "
	 << macro->name << " is some error \"constant\"" << endl;
    csl.overrideLoc(macro->preStartLoc);
    endcsl.overrideLoc( macro->preEndLoc);
    patcher.printPatch("nsnull", PairLoc(csl, endcsl));
    return;
  }
  
  std::string strRet;
  PairLoc pairRet(csl, endcsl);
  // TODO, check that nsresult is the cast expr
  if (E_cast *c = e->ifE_cast()) {
    e = c->expr;
  }
  E_intLit *i = e->ifE_intLit();
  
  if (!i) {
    E_funCall *func = e->ifE_funCall();
    if (func && isMatchingCall(func)) {
      Restorer<Statement*> restorer(outmostStatement, s);
      postvisitE_funCall(func);
      return;
    }
    strRet = patcher.getRange(pairRet) + " == NS_OK ? "
      + "__" + currentOpi->outparamName + " : nsnull";
  } else if (i->i != 0) {
    strRet = "nsnull";
  }
  
  if (strRet.empty()) {
    strRet = "__" + currentOpi->outparamName;
  }
  
  patcher.printPatch(strRet, pairRet);
}

static Expression* unrollParam(Expression *inExpr, Expression *&outOuterExpr,
                               OutParamInfo const &opi) {
  Expression *outExpr = inExpr;
  outOuterExpr = outExpr;
  // loop does "recursion"
  while(true) {
    switch(outExpr->kind()) {
    case Expression::E_VARIABLE:
    case Expression::E_FIELDACC:
      return outExpr;
    case Expression::E_ADDROF:
      outOuterExpr = outExpr;
      outExpr = outExpr->asE_addrOf()->expr;
      break;
    case Expression::E_CAST:
      outOuterExpr = outExpr;
      outExpr = outExpr->asE_cast()->expr;
      break;
    case Expression::E_KEYWORDCAST:
      outExpr = outExpr->asE_keywordCast()->expr;
      break;
    case Expression::E_FUNCALL:
      {
	E_funCall *f = outExpr->asE_funCall();
	static const StringRef strGetterAddRefs = globalStrTable("getter_AddRefs");
	static const StringRef strGetter_Transfers = globalStrTable("getter_Transfers");
	E_variable *v = f->func->ifE_variable();
	if (v && (v->var->name == strGetterAddRefs ||  v->var->name == strGetter_Transfers)) {
	  outOuterExpr = outExpr;
	  outExpr = f->args->first()->expr;
	  break;
	} else if (v) {
          cerr << opi.memberName << " used with a weird wrapper: "
               << v->var->name << endl;
        }
        
      }
    default:
      xassert(false && "Unhandled outparameter argument expression");
    }
  }
  
  return outExpr;
}

// Takes outparam expression and returns LHS of form outparam = (foo)
std::string OutparamDel::getOutparamWithCast(Expression *inExpr,
                                             std::string &strGetterAddrefsCast,
                                             OutParamInfo const &opi)
{
  // this will contain the most important outer expr
  // ie E_funCall, E_*cast
  Expression *outerExpr = NULL;
  Expression *outExpr = unrollParam(inExpr, outerExpr, opi);
  std::string strExpr;
  std::string strType;
  std::string::size_type pos;
  bool getterAddRefs(false);
  strGetterAddrefsCast.resize(0);

  switch(outerExpr->kind()) {
  case Expression::E_ADDROF:
    strType = "(" + std::string(outExpr->type->toString().c_str()) + ") ";
    break;
  // this is for getter_AddRefs
  case Expression::E_FUNCALL:
    {
      // work with inExpr, since it's a simplified version in case of getter_AddRefs
      TemplateInfo *ti = 
	outerExpr->type->asCVAtomicType()->atomic->asCompoundType()->templateInfo();
      static const StringRef str_nsGetterAddRefs = globalStrTable("nsGetterAddRefs");
      static const StringRef str_nsRefPtrGetterAddRefs = 
        globalStrTable("nsRefPtrGetterAddRefs");
      static const StringRef str_nsAutoPtrGetterTransfers = 
        globalStrTable("nsAutoPtrGetterTransfers");
      static const StringRef str_nsAutoArrayPtrGetterTransfers = 
        globalStrTable("nsAutoArrayPtrGetterTransfers");
      StringRef var_name = ti->var->name;
      xassert((var_name == str_nsGetterAddRefs
               || var_name == str_nsRefPtrGetterAddRefs
               || var_name == str_nsAutoPtrGetterTransfers
               || var_name == str_nsAutoArrayPtrGetterTransfers)
	      && ti->arguments.count() == 1);
      STemplateArgument *ta = ti->arguments.first();
      xassert(ta->isType());
      Type *innerType = ta->getType();
      strType = innerType->toString().c_str();
      getterAddRefs = true;
      //strType.append("*");
    }
    break;
  case Expression::E_VARIABLE:
  case Expression::E_CAST:
  case Expression::E_KEYWORDCAST:
    strType = "(" + std::string(outExpr->type->toString().c_str()) + ") ";
    pos = strType.rfind('*');
    xassert(pos != std::string::npos);
    strType.erase(pos, 1);
    //value needs to be dereferenced on LHS
    strExpr = "*";
    break;
  default:
    xassert(false && "unhandled outparamter argument..when looking for type");
    break;
  }
  // silly & on arguments sometimes
  pos = strType.rfind('&');
  if (pos != std::string::npos) {
    strType.erase(pos, 1);  
  }
  
  CPPSourceLoc cslOutparam(outExpr->loc);
  CPPSourceLoc endcslOutparam(outExpr->endloc);
  strExpr += patcher.getRange(PairLoc(cslOutparam, endcslOutparam));
  strExpr.append(" = ");
  // then we aren't rewriting into identical code
  // and creating a leak
  if (cmd.warn && getterAddRefs != opi.already_AddRefed) {
    OutParamInfo myopi = opi;
    myopi.already_AddRefed = true;
    cerr << "Rewrite needs to specify already_AddRefed: " << myopi.toString() << endl;
  }
  if (!getterAddRefs) {
    strExpr.append(strType);
    //don't need to use special version of getter if types already match
  } else if (opi.paramTypes[opi.param].find(strType) != 0) {
    strGetterAddrefsCast = strType;
  }
  return strExpr;
}

OutParamInfo* OutparamDel::isMatchingCall(E_funCall *e) {
  E_fieldAcc *f = e->func->ifE_fieldAcc();
  if (!f || !f->field) return NULL;
  
  outparam_map::iterator it = mapOutparams.find(f->field);
  if (it == mapOutparams.end()) return NULL;

  return it->second;
}

void OutparamDel::postvisitE_funCall(E_funCall *e) {
  OutParamInfo *opi = isMatchingCall(e);
  if (!opi) return;

  unsigned int counter = opi->param;
  Expression *paramExpr = NULL;
  // point at the argument in front of outparam
  Expression *priorParamExpr = NULL;
  for (FakeList<ArgExpression>* args = e->args;
	 ! args->isEmpty();
	 args = args->butFirst()) {
    if (0 != counter--) {
      priorParamExpr = args->first()->expr;
      continue;
    }
    paramExpr = args->first()->expr;
    break;
  }

  CPPSourceLoc cslStart(e->loc);
  CPPSourceLoc cslParamStart(paramExpr->loc);
  CPPSourceLoc cslParamEnd(paramExpr->endloc);
  CPPSourceLoc cslEnd(e->endloc);
  MacroUndoEntry *macro = NULL;
  if (!cslStart.hasExactPosition()) {
    macro = cslStart.macroExpansion;
  } else if (!cslParamStart.hasExactPosition()) {
    macro = cslParamStart.macroExpansion;
  } else if (!cslParamEnd.hasExactPosition()) {
    macro = cslParamEnd.macroExpansion;
  } else if (!cslEnd.hasExactPosition()) {
    macro = cslEnd.macroExpansion;
  }
  if (macro) {
    cerr << toString(cslStart.loc()) << ": "
	 << opi->memberName << " called within "<< macro->name << endl;
    return;
  }
  // first strip the outparam
  PairLoc pairFuncToOutparam(cslStart, cslParamStart);
  PairLoc pairParamToEnd(cslParamEnd, cslEnd);

  std::string strFuncToOutparam = patcher.getRange(pairFuncToOutparam);
  std::string::size_type pos = strFuncToOutparam.find_last_of("(,");
  xassert(pos != std::string::npos);

  // Erase text prior to outparam.
  // don't erase '(', but do erase ',' and everything else
  if (strFuncToOutparam[pos] == '(') {
    pos++;
  }
  strFuncToOutparam.erase(pos);
  std::string strOutparamToEnd = patcher.getRange(pairParamToEnd);
  pos = strOutparamToEnd.find_first_of(",)");
  xassert(pos != std::string::npos);
  
  // if outparam is the first paramter out of many, drop the comma after it
  if (e->args->count() > 1 && opi->param == 0) {
    do {
      pos++;
      // also skip whitespace after comma
    } while (pos < strOutparamToEnd.size() && isspace(strOutparamToEnd[pos]));
  }
  strOutparamToEnd.erase(0,pos);

  std::string strGetterAddrefsCast;
  std::string strLHS = getOutparamWithCast(paramExpr, strGetterAddrefsCast, *opi);
  std::string strExpr = strLHS + strFuncToOutparam + strOutparamToEnd;

  static const StringRef strNS_FAILED = globalStrTable("NS_FAILED");
  static const StringRef strNS_SUCCEEDED = globalStrTable("NS_SUCCEEDED");
  macro = cslStart.macroExpansion;
  E_variable *evar = outmostExpression && outmostExpression->isE_funCall() 
    ? outmostExpression->asE_funCall()->func->ifE_variable()
    : NULL;
  Variable *v = evar ? evar->var : NULL;
  bool outerExpHandled = false;
  if (!strGetterAddrefsCast.empty()) {
    CPPSourceLoc cslEoFunc(e->func->endloc);
    PairLoc pairFunc(cslStart, cslEoFunc);
    std::string strFuncToParam = patcher.getRange(pairFunc); 
    strExpr = strLHS;
      
    // this is a QI-specific hack
    if (opi->memberName == "QueryInterface") {
      strExpr.append(strFuncToParam);
      strExpr.append("AAR<");
      strExpr.append(strGetterAddrefsCast);
      strExpr.append(">");
    } else {
      strExpr.append("(");
      strExpr.append(strGetterAddrefsCast);
      strExpr.append("*)");
      strExpr.append(strFuncToParam);
    }
    if (priorParamExpr) {
      CPPSourceLoc endcslPrior(priorParamExpr->endloc);
      if (endcslPrior.macroExpansion) {
        endcslPrior.overrideLoc(endcslPrior.macroExpansion->preEndLoc);
      }
      strExpr.append(patcher.getRange(PairLoc(cslEoFunc, endcslPrior)));
    } else {
      strExpr.append("(");
    }
    strExpr.append(strOutparamToEnd);
  } else if (currentOpi && outmostStatement && outmostStatement->isS_return()) {
    strExpr = strFuncToOutparam + strOutparamToEnd;
    outerExpHandled = true;
  } else if (v && (v->name == strNS_FAILED || v->name == strNS_SUCCEEDED)) {
    strExpr = (v->name == strNS_FAILED ? "! (" : "!! (") + strExpr + ")";
    cslStart = CPPSourceLoc(outmostExpression->loc);
    cslEnd = CPPSourceLoc(outmostExpression->endloc);
    outerExpHandled = true;
  } 
  if (!outerExpHandled && (outmostExpression || outmostStatement)) {
    strExpr = "((" + strExpr + ") ? NS_OK : "
      + cmd.outparamError
      + ")";
  }
  patcher.printPatch(strExpr, PairLoc(cslStart, cslEnd));
}

bool OutparamDel::visitExpression(Expression *e) {
  if (ExpressionVisitor::visitExpression(e)) {
    if (!outmostExpression) outmostExpression = e;
    return true;
  }
  return false;
}

void OutparamDel::postvisitExpression(Expression *e) {
  if (outmostExpression == e) {
    outmostExpression = NULL;
  }
  ExpressionVisitor::postvisitExpression(e);
}

bool OutparamDel::visitStatement(Statement *s) {
  // This is a hack
  // proper solution would be to manually traverse the
  // conditional(or assignment) part of statements
  // this way their bodies won't get special treatment too
  switch(s->kind()) {
  case Statement::S_DECL:
  case Statement::S_RETURN:
    if (!outmostStatement) {
      outmostStatement = s;
    }
  default:
    return ExpressionVisitor::visitStatement(s);
  }
  return true;
}

bool OutparamDel::visitS_if(S_if *s) {
  // give special treatment to conditional expr
  if (s->cond) { 
    Restorer<Statement*> restorer(outmostStatement, s);
    s->cond->traverse(this->loweredVisitor); 
  }

  // rest is just like S_if::traverse
  if (s->thenBranch) { 
    s->thenBranch->traverse(this->loweredVisitor); 
  }
  if (s->elseBranch) { 
    s->elseBranch->traverse(this->loweredVisitor); 
  }
  return false;
}

bool OutparamDel::visitS_while(S_while *s) {
  // give special treatment to conditional expr
  if (s->cond) { 
    Restorer<Statement*> restorer(outmostStatement, s);
    s->cond->traverse(this->loweredVisitor); 
  }

  // rest is just like S_while::traverse
  if (s->body) { 
    s->body->traverse(this->loweredVisitor); 
  }
  return false;
}

bool OutparamDel::visitS_doWhile(S_doWhile *s) {
  // just like S_doWhile::traverse
  if (s->body) { 
    s->body->traverse(this->loweredVisitor); 
  }
  
  // give special treatment to conditional expr
  if (s->expr) { 
    Restorer<Statement*> restorer(outmostStatement, s);
    s->expr->traverse(this->loweredVisitor); 
  }
  return false;
}

bool OutparamDel::visitS_for(S_for *s) {
  // give special treatment to conditional expr
  { 
    Restorer<Statement*> restorer(outmostStatement, s);
    if (s->init) s->init->traverse(this->loweredVisitor);
    if (s->cond) s->cond->traverse(this->loweredVisitor);
  }  
  // rest is just like S_for::traverse
  if (s->after) { 
    s->after->traverse(this->loweredVisitor); 
  }
  if (s->body) { 
    s->body->traverse(this->loweredVisitor); 
  }
  return false;
}

void OutparamDel::postvisitStatement(Statement *s) {
  if (outmostStatement == s) outmostStatement = NULL;
  ExpressionVisitor::postvisitStatement(s);
}
