package soot.dex;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import soot.ArrayType;
import soot.Body;
import soot.BodyTransformer;
import soot.Local;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.jimple.ArrayRef;
import soot.jimple.AssignStmt;
import soot.jimple.CastExpr;
import soot.jimple.Constant;
import soot.jimple.FieldRef;
import soot.jimple.IdentityStmt;
import soot.jimple.InvokeExpr;
import soot.jimple.NewArrayExpr;
import soot.jimple.Stmt;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.scalar.LocalDefs;
import soot.toolkits.scalar.LocalUses;
import soot.toolkits.scalar.SimpleLocalUses;
import soot.toolkits.scalar.SmartLocalDefs;
import soot.toolkits.scalar.UnitValueBoxPair;

public abstract class DexTransformer extends BodyTransformer {

  /**
   * Collect definitions of l in body including the definitions of aliases of l.
   *
   * In this context an alias is a local that propagates its value to l.
   *
   * @param l the local whose definitions are to collect
   * @param localDefs the LocalDefs object
   * @param body the body that contains the local
   */
  protected List<Unit> collectDefinitionsWithAliases(Local l, LocalDefs localDefs, LocalUses localUses, Body body) {
    Set<Local> seenLocals = new HashSet<Local>();
    Stack<Local> newLocals = new Stack<Local>();
    List<Unit> defs = new LinkedList<Unit>();
    newLocals.push(l);

    while (!newLocals.empty()) {
      Local local = newLocals.pop();
      Debug.printDbg("[null local] "+ local);
      if (seenLocals.contains(local))
        continue;
      for (Unit u : collectDefinitions(local, localDefs, body)) {
        if (u instanceof AssignStmt) {
          Value r = ((AssignStmt) u).getRightOp();
          if (r instanceof Local && ! seenLocals.contains((Local) r))
            newLocals.push((Local) r);
        }
        defs.add(u);
        //
        for (UnitValueBoxPair pair : (List<UnitValueBoxPair>) localUses.getUsesOf(u)) {
          Unit unit = pair.getUnit();
          if (unit instanceof AssignStmt) {
            Value right = ((AssignStmt) unit).getRightOp();
            Value left = ((AssignStmt) unit).getLeftOp();
            if (right == local  && left instanceof Local && ! seenLocals.contains((Local) left))
              newLocals.push((Local) left);
          }
        }
        //
      }
      seenLocals.add(local);
    }
    return defs;
  }

  /**
   * Convenience method that collects all definitions of l.
   *
   * @param l the local whose definitions are to collect
   * @param localDefs the LocalDefs object
   * @param body the body that contains the local
   */
  private List<Unit> collectDefinitions(Local l, LocalDefs localDefs, Body body) {
    List <Unit> defs = new LinkedList<Unit>();
    for (Unit u : body.getUnits()) {
      List<Unit> defsOf = localDefs.getDefsOfAt(l, u);
      if (defsOf != null)
        defs.addAll(defsOf);
    }
    for (Unit u: defs) {
      Debug.printDbg("[add def] "+ u);
    }
    return defs;
  }

  protected Type findArrayType(ExceptionalUnitGraph g,
      SmartLocalDefs localDefs, SimpleLocalUses localUses, Stmt arrayStmt) {
    ArrayRef aRef = null;
    if (arrayStmt.containsArrayRef()) {
      aRef = arrayStmt.getArrayRef();
    }
    Local aBase = null;

    if (null == aRef) {
      if (arrayStmt instanceof AssignStmt) {
        AssignStmt stmt = (AssignStmt)arrayStmt;
        aBase = (Local)stmt.getRightOp();
      } else {
        System.out.println("ERROR: not an assign statement: "+ arrayStmt);
        System.exit(-1);
      }
    } else {    
      aBase = (Local)aRef.getBase();
    }

    List<Unit> defsOfaBaseList = localDefs.getDefsOfAt(aBase, arrayStmt);
    if (defsOfaBaseList == null || defsOfaBaseList.size() == 0) {
      System.out.println("ERROR: no def statement found for array base local "+ arrayStmt);
      System.exit(-1);
    }

    // We should find an answer only by processing the first item of the list
    for (Unit baseDef: defsOfaBaseList) {

      // baseDef is either an assignment statement or an identity statement
      if (baseDef instanceof AssignStmt) {
        AssignStmt stmt = (AssignStmt) baseDef;
        Value r = stmt.getRightOp();
        if (r instanceof FieldRef) {
          Type t = ((FieldRef) r).getFieldRef().type();
          if (t instanceof ArrayType) {
            ArrayType at = (ArrayType)t;
            t = at.getArrayElementType();
          }
          Debug.printDbg("atype fieldref: "+ t);
          return t;
        } else if (r instanceof ArrayRef) {
          ArrayRef ar = (ArrayRef)r;
          if (ar.getType().equals(".unknown") || ar.getType().toString().equals("unknown")) { // || ar.getType()) {
            System.out.println("second round from stmt: "+ stmt);
            Type t = findArrayType (g, localDefs, localUses, stmt); //TODO: which type should be returned? //TODO: /!\ loops
            if (t instanceof ArrayType) {
              ArrayType at = (ArrayType)t;
              t = at.getArrayElementType();
            }
            return t;
          } else {
            Debug.printDbg("atype arrayref: "+ ar.getType().toString());
            ArrayType at = (ArrayType) stmt.getRightOp().getType();
            return at.getArrayElementType();
            //return ar.getType();                         
          }
        } else if (r instanceof NewArrayExpr) {
          NewArrayExpr expr = (NewArrayExpr)r;
          Type t = expr.getBaseType();
          Debug.printDbg("atype newarrayexpr: "+ t);
          return t;
        } else if (r instanceof CastExpr) {
          Type t = (((CastExpr)r).getCastType());
          Debug.printDbg("atype cast: "+ t);
          return t;
        } else if (r instanceof InvokeExpr) {
          Type t = ((InvokeExpr) r).getMethodRef().returnType();
          Debug.printDbg("atype invoke: "+ t);
          return t;
          // introduces alias
        } else if (r instanceof Local) {
          Debug.printDbg("atype alias: "+ stmt);
          return findArrayType (g, localDefs, localUses, stmt);
        } else if (r instanceof Constant) {
          return null;
        } else {
          throw new RuntimeException("ERROR: def statement not possible! "+ stmt);
        }

      } else if (baseDef instanceof IdentityStmt) {
        IdentityStmt stmt = (IdentityStmt)baseDef;
        ArrayType at = (ArrayType) stmt.getRightOp().getType();
        return at.getArrayElementType();
      } else {
        throw new RuntimeException("ERROR: base local def must be AssignStmt or IdentityStmt! "+ baseDef);
      }

    } // loop 
    throw new RuntimeException("ERROR: could not find type of array from statement '"+ arrayStmt +"'");

  }

}

@@ -6198,31 +6198,8 @@
 ) %7B%0A
-          return null;%0A
     

Check all defs for finding array type.