package soot.dexpler;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 1997 - 2018 Raja Vallée-Rai and others
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.util.Iterator;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import soot.Body;
import soot.BodyTransformer;
import soot.Local;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.ArrayRef;
import soot.jimple.AssignStmt;
import soot.jimple.Constant;
import soot.jimple.Stmt;
import soot.options.Options;
import soot.toolkits.scalar.UnusedLocalEliminator;

/**
 * Transformer that simplifies array initializations. It converts
 *
 * a = 0 b = 42 c[a] = b
 *
 * to
 *
 * c[0] 42
 *
 * This transformer performs copy propagation, dead assignment elimination, and unused local elimination at once for this
 * special case. The idea is to reduce the code as much as possible for this special case before applying the more expensive
 * other transformers.
 *
 * @author Steven Arzt
 *
 */
public class DexArrayInitReducer extends BodyTransformer {
  private static final Logger logger = LoggerFactory.getLogger(DexArrayInitReducer.class);

  public static DexArrayInitReducer v() {
    return new DexArrayInitReducer();
  }

  @Override
  protected void internalTransform(Body b, String phaseName, Map<String, String> options) {
    // Make sure that we only have linear control flow
    if (!b.getTraps().isEmpty()) {
      return;
    }

    // Look for a chain of two constant assignments followed by an array put
    Unit u1 = null, u2 = null;
    for (Iterator<Unit> uIt = b.getUnits().snapshotIterator(); uIt.hasNext();) {
      Unit u = uIt.next();

      // If this is not an assignment, it does not matter.
      if (!(u instanceof AssignStmt) || !((Stmt) u).getBoxesPointingToThis().isEmpty()) {
        u1 = null;
        u2 = null;
        continue;
      }

      // If this is an assignment to an array, we must already have two
      // preceding constant assignments
      AssignStmt assignStmt = (AssignStmt) u;
      if (assignStmt.getLeftOp() instanceof ArrayRef) {
        if (u1 != null && u2 != null && u2.getBoxesPointingToThis().isEmpty()
            && assignStmt.getBoxesPointingToThis().isEmpty()) {
          ArrayRef arrayRef = (ArrayRef) assignStmt.getLeftOp();

          Value u1val = u1.getDefBoxes().get(0).getValue();
          Value u2val = u2.getDefBoxes().get(0).getValue();

          // index
          if (arrayRef.getIndex() == u1val) {
            arrayRef.setIndex(((AssignStmt) u1).getRightOp());
          } else if (arrayRef.getIndex() == u2val) {
            arrayRef.setIndex(((AssignStmt) u2).getRightOp());
          }

          // value
          if (assignStmt.getRightOp() == u1val) {
            assignStmt.setRightOp(((AssignStmt) u1).getRightOp());
          } else if (assignStmt.getRightOp() == u2val) {
            assignStmt.setRightOp(((AssignStmt) u2).getRightOp());
          }

          // Remove the unnecessary assignments
          Iterator<Unit> checkIt = b.getUnits().iterator(u);
          boolean foundU1 = false, foundU2 = false, doneU1 = false, doneU2 = false;
          while (!(doneU1 && doneU2) && !(foundU1 && foundU2) && checkIt.hasNext()) {
            Unit checkU = checkIt.next();

            // Does the current statement use the value?
            for (ValueBox vb : checkU.getUseBoxes()) {
              if (!doneU1 && vb.getValue() == u1val) {
                foundU1 = true;
              }
              if (!doneU2 && vb.getValue() == u2val) {
                foundU2 = true;
              }
            }

            // Does the current statement overwrite the value?
            for (ValueBox vb : checkU.getDefBoxes()) {
              if (vb.getValue() == u1val) {
                doneU1 = true;
              } else if (vb.getValue() == u2val) {
                doneU2 = true;
              }
            }

            // If this statement branches, we abort
            if (checkU.branches()) {
              foundU1 = true;
              foundU2 = true;
              break;
            }
          }
          if (!foundU1) {
            // only remove constant assignment if the left value is Local
            if (u1val instanceof Local) {
              b.getUnits().remove(u1);
              if (Options.v().verbose()) {
                logger.debug("[" + b.getMethod().getName() + "]    remove 1 " + u1);
              }
            }
          }
          if (!foundU2) {
            // only remove constant assignment if the left value is Local
            if (u2val instanceof Local) {
              b.getUnits().remove(u2);
              if (Options.v().verbose()) {
                logger.debug("[" + b.getMethod().getName() + "]    remove 2 " + u2);
              }
            }
          }

          u1 = null;
          u2 = null;
        } else {
          // No proper initialization before
          u1 = null;
          u2 = null;
          continue;
        }
      }

      // We have a normal assignment. This could be an array index or
      // value.
      if (!(assignStmt.getRightOp() instanceof Constant)) {
        u1 = null;
        u2 = null;
        continue;
      }

      if (u1 == null) {
        u1 = assignStmt;
      } else if (u2 == null) {
        u2 = assignStmt;

        // If the last value is overwritten again, we start again at the beginning
        if (u1 != null) {
          Value op1 = ((AssignStmt) u1).getLeftOp();
          if (op1 == ((AssignStmt) u2).getLeftOp()) {
            u1 = u2;
            u2 = null;
          }
        }
      } else {
        u1 = u2;
        u2 = assignStmt;
      }
    }

    // Remove all locals that are no longer necessary
    UnusedLocalEliminator.v().transform(b);
  }

}
