All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sootup.java.bytecode.interceptors.DeadAssignmentEliminator Maven / Gradle / Ivy

There is a newer version: 1.3.0
Show newest version
package sootup.java.bytecode.interceptors;
/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 1997-2020 Raja Vallée-Rai, Christian Brüggemann
 * %%
 * 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
 * .
 * #L%
 */
import java.util.*;
import javax.annotation.Nonnull;
import sootup.core.graph.MutableBasicBlock;
import sootup.core.graph.StmtGraph;
import sootup.core.jimple.Jimple;
import sootup.core.jimple.basic.Local;
import sootup.core.jimple.basic.Value;
import sootup.core.jimple.common.constant.IntConstant;
import sootup.core.jimple.common.constant.NullConstant;
import sootup.core.jimple.common.expr.*;
import sootup.core.jimple.common.ref.JArrayRef;
import sootup.core.jimple.common.ref.JFieldRef;
import sootup.core.jimple.common.ref.JInstanceFieldRef;
import sootup.core.jimple.common.stmt.JAssignStmt;
import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.Body;
import sootup.core.model.Modifier;
import sootup.core.transform.BodyInterceptor;
import sootup.core.types.*;
import sootup.core.views.View;

/**
 * This interceptor eliminates assignment statements to locals whose values are not subsequently
 * used, unless evaluating the right-hand side of the assignment may cause side-effects. Complexity
 * is linear with respect to the statements.
 *
 * @author Marcus Nachtigall
 */
public class DeadAssignmentEliminator implements BodyInterceptor {

  // eliminateOnlyStackLocals: locals which are: nulltype or not referencing a field (ms: possibly
  // more?)
  boolean eliminateOnlyStackLocals;

  public DeadAssignmentEliminator() {
    this(false);
  }

  public DeadAssignmentEliminator(boolean eliminateOnlyStackLocals) {
    this.eliminateOnlyStackLocals = eliminateOnlyStackLocals;
  }

  Map> allDefs = new HashMap<>();
  Map> allUses = new HashMap<>();

  @Override
  public void interceptBody(@Nonnull Body.BodyBuilder builder, @Nonnull View view) {
    StmtGraph stmtGraph = builder.getStmtGraph();
    List stmts = builder.getStmts();
    Deque deque = new ArrayDeque<>(stmts.size());

    // Make a first pass through the statements, noting the statements we must absolutely keep

    boolean isStatic = Modifier.isStatic(builder.getModifiers());
    boolean allEssential = true;
    boolean containsInvoke = false;
    Local thisLocal = null;

    for (Iterator iterator = stmtGraph.getNodes().iterator(); iterator.hasNext(); ) {
      Stmt stmt = iterator.next();
      boolean isEssential = true;

      if (stmt instanceof JAssignStmt) {
        JAssignStmt assignStmt = (JAssignStmt) stmt;
        Value lhs = assignStmt.getLeftOp();
        Value rhs = assignStmt.getRightOp();

        // Stmt is of the form a = a which is useless
        if (lhs == rhs && lhs instanceof Local) {
          iterator.remove();
          continue;
        }

        if (lhs instanceof Local
            && (!eliminateOnlyStackLocals
                || ((Local) lhs).getName().startsWith("$")
                || lhs.getType() instanceof NullType)) {
          // FIXME: [ms] inspection says right side of && is always true
          isEssential = false;

          if (!containsInvoke) {
            // performance optimization: to not repeat containsInvokeExpr()
            containsInvoke = assignStmt.containsInvokeExpr();
          }

          if (rhs instanceof JCastExpr) {
            // CastExpr: can trigger ClassCastException, but null-casts never fail
            JCastExpr castExpr = (JCastExpr) rhs;
            Type type = castExpr.getType();
            Value value = castExpr.getOp();
            isEssential = !(value instanceof NullConstant) && type instanceof ReferenceType;
          } else if (rhs instanceof AbstractInvokeExpr
              || rhs instanceof JArrayRef
              || rhs instanceof JNewExpr
              || rhs instanceof JNewArrayExpr
              || rhs instanceof JNewMultiArrayExpr) {
            // InvokeExpr: can have side effects (like throwing a null pointer exception)
            // JArrayRef: can have side effects (like throwing a null pointer exception)
            // JNewExpr: can trigger class initialization
            // JNewArrayExpr: can throw exception
            // JNewMultiArrayExpr: can throw exception
            isEssential = true;
          } else if (rhs instanceof JFieldRef) {
            // can trigger class initialization
            isEssential = true;

            if (rhs instanceof JInstanceFieldRef) {
              JInstanceFieldRef instanceFieldRef = (JInstanceFieldRef) rhs;
              if (!isStatic && thisLocal == null) {
                thisLocal = Body.getThisLocal(stmtGraph);
              }

              // Any JInstanceFieldRef may have side effects, unless the base is reading from 'this'
              // in a non-static method
              isEssential = (isStatic || thisLocal != instanceFieldRef.getBase());
            }
          } else if (rhs instanceof JDivExpr || rhs instanceof JRemExpr) {
            AbstractBinopExpr expr = (AbstractBinopExpr) rhs;
            Type type1 = expr.getOp1().getType();
            Type type2 = expr.getOp2().getType();

            // Can trigger a division by zero
            boolean type2Int =
                type2 instanceof PrimitiveType && type2.equals(PrimitiveType.getInt());
            isEssential =
                type2Int
                    || type1 instanceof PrimitiveType
                        && (type1.equals(PrimitiveType.getInt())
                            || type1.equals(PrimitiveType.getLong()))
                    || type2 instanceof PrimitiveType && type2.equals(PrimitiveType.getLong())
                    || type1 instanceof UnknownType
                    || type2 instanceof UnknownType;

            if (isEssential && type2Int) {
              Value value = expr.getOp2();
              if (value instanceof IntConstant) {
                IntConstant intConstant = (IntConstant) value;
                isEssential = (intConstant.getValue() == 0);
              } else {
                // [ms] oh the irony..
                isEssential = true; // could be 0, we don't know
              }
            }
          }
        }
      }

      if (isEssential) {
        deque.addFirst(stmt);
      }

      allEssential &= isEssential;
    }

    if (containsInvoke || !allEssential) {
      // Add all the statements which are used to compute values for the essential statements,
      // recursively
      allDefs = Body.collectDefs(builder.getStmts());

      if (!allEssential) {
        Set essentialStmts = new HashSet<>(stmts.size());
        while (!deque.isEmpty()) {
          Stmt stmt = deque.removeFirst();
          if (essentialStmts.add(stmt)) {
            for (Value value : stmt.getUses()) {
              if (value instanceof Local) {
                Local local = (Local) value;
                Collection defs = allDefs.get(local);
                if (defs != null) {
                  deque.addAll(defs);
                }
              }
            }
          }
        }

        // Remove the dead statements
        for (Stmt stmt : stmts) {
          if (!essentialStmts.contains(stmt)) {
            builder.removeStmt(stmt);
          }
        }
      }

      if (containsInvoke) {
        allUses = Body.collectUses(builder.getStmts());
        // Eliminate dead assignments from invokes such as x = f(), where x is no longer used
        List> postProcess = new ArrayList<>();
        for (Stmt stmt : stmts) {
          if (stmt instanceof JAssignStmt) {
            JAssignStmt assignStmt = (JAssignStmt) stmt;
            if (assignStmt.containsInvokeExpr()) {
              // Just find one use of local which is essential
              boolean deadAssignment = true;
              Local local = (Local) assignStmt.getRightOp();
              for (Stmt use : allUses.get(local)) {
                if (builder.getStmts().contains(use)) {
                  deadAssignment = false;
                  break;
                }
              }
              if (deadAssignment) {
                postProcess.add(assignStmt);
              }
            }
          }
        }

        for (JAssignStmt assignStmt : postProcess) {
          // Transform it into a simple invoke
          Stmt newInvoke =
              Jimple.newInvokeStmt(assignStmt.getInvokeExpr(), assignStmt.getPositionInfo());
          builder.replaceStmt(assignStmt, newInvoke);
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy