Compiler now inlines many more types of methods in the Java AST.  Includes new tests to check for inlining bugs.

Issue: #1787
Patch by: mmastrac, scottb
Review by: scottb, mmastrac


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1507 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
new file mode 100644
index 0000000..4d815eb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
+import com.google.gwt.dev.jjs.ast.JArrayRef;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JCastOperation;
+import com.google.gwt.dev.jjs.ast.JCharLiteral;
+import com.google.gwt.dev.jjs.ast.JClassLiteral;
+import com.google.gwt.dev.jjs.ast.JConditional;
+import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JFloatLiteral;
+import com.google.gwt.dev.jjs.ast.JInstanceOf;
+import com.google.gwt.dev.jjs.ast.JIntLiteral;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
+import com.google.gwt.dev.jjs.ast.JLongLiteral;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JNewArray;
+import com.google.gwt.dev.jjs.ast.JNewInstance;
+import com.google.gwt.dev.jjs.ast.JNullLiteral;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JPostfixOperation;
+import com.google.gwt.dev.jjs.ast.JPrefixOperation;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JStringLiteral;
+import com.google.gwt.dev.jjs.ast.JThisRef;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.js.JClassSeed;
+import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
+
+import java.util.ArrayList;
+
+/**
+ * A general purpose expression cloner.
+ */
+public class CloneExpressionVisitor extends JVisitor {
+  private JExpression expression;
+  private JProgram program;
+
+  public CloneExpressionVisitor(JProgram program) {
+    this.program = program;
+  }
+
+  public JExpression cloneExpression(JExpression expr) {
+    if (expr == null) {
+      return null;
+    }
+
+    this.accept(expr);
+
+    if (expression == null) {
+      throw new InternalCompilerException(expr, "Unable to clone expression",
+          null);
+    }
+
+    return expression;
+  }
+
+  @Override
+  public boolean visit(JAbsentArrayDimension x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JArrayRef x, Context ctx) {
+    expression = new JArrayRef(program, x.getSourceInfo(),
+        cloneExpression(x.getInstance()), cloneExpression(x.getIndexExpr()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(JBinaryOperation x, Context ctx) {
+    expression = new JBinaryOperation(program, x.getSourceInfo(), x.getType(),
+        x.getOp(), cloneExpression(x.getLhs()), cloneExpression(x.getRhs()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(JBooleanLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JCastOperation x, Context ctx) {
+    expression = new JCastOperation(program, x.getSourceInfo(),
+        x.getCastType(), cloneExpression(x.getExpr()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(JCharLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JClassLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JClassSeed x, Context ctx) {
+    expression = new JClassSeed(program, x.getRefType());
+    return false;
+  }
+
+  @Override
+  public boolean visit(JConditional x, Context ctx) {
+    expression = new JConditional(program, x.getSourceInfo(), x.getType(),
+        cloneExpression(x.getIfTest()), cloneExpression(x.getThenExpr()),
+        cloneExpression(x.getElseExpr()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(JDoubleLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JFieldRef x, Context ctx) {
+    expression = new JFieldRef(program, x.getSourceInfo(),
+        cloneExpression(x.getInstance()), x.getField(), x.getEnclosingType());
+    return false;
+  }
+
+  @Override
+  public boolean visit(JFloatLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JInstanceOf x, Context ctx) {
+    expression = new JInstanceOf(program, x.getSourceInfo(), x.getTestType(),
+        cloneExpression(x.getExpr()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(JIntLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JLocalRef x, Context ctx) {
+    expression = new JLocalRef(program, x.getSourceInfo(), x.getLocal());
+    return false;
+  }
+
+  @Override
+  public boolean visit(JLongLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JMethodCall x, Context ctx) {
+    JMethodCall newMethodCall = new JMethodCall(program, x.getSourceInfo(),
+        cloneExpression(x.getInstance()), x.getTarget());
+
+    for (JExpression arg : x.getArgs()) {
+      newMethodCall.getArgs().add(cloneExpression(arg));
+    }
+
+    expression = newMethodCall;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JMultiExpression x, Context ctx) {
+    JMultiExpression multi = new JMultiExpression(program, x.getSourceInfo());
+    for (JExpression expr : x.exprs) {
+      multi.exprs.add(cloneExpression(expr));
+    }
+
+    expression = multi;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JNewArray x, Context ctx) {
+    JNewArray newArray = new JNewArray(program, x.getSourceInfo(),
+        x.getArrayType());
+
+    if (x.dims != null) {
+      newArray.dims = new ArrayList<JExpression>();
+      for (JExpression dim : x.dims) {
+        newArray.dims.add(cloneExpression(dim));
+      }
+    }
+    if (x.initializers != null) {
+      newArray.initializers = new ArrayList<JExpression>();
+      for (JExpression initializer : x.initializers) {
+        newArray.initializers.add(cloneExpression(initializer));
+      }
+    }
+
+    expression = newArray;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JNewInstance x, Context ctx) {
+    expression = new JNewInstance(program, x.getSourceInfo(), x.getClassType());
+    return false;
+  }
+
+  @Override
+  public boolean visit(JNullLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JParameterRef x, Context ctx) {
+    expression = new JParameterRef(program, x.getSourceInfo(), x.getParameter());
+    return false;
+  }
+
+  @Override
+  public boolean visit(JPostfixOperation x, Context ctx) {
+    expression = new JPostfixOperation(program, x.getSourceInfo(), x.getOp(),
+        cloneExpression(x.getArg()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(JPrefixOperation x, Context ctx) {
+    expression = new JPrefixOperation(program, x.getSourceInfo(), x.getOp(),
+        cloneExpression(x.getArg()));
+    return false;
+  }
+
+  @Override
+  public boolean visit(JStringLiteral x, Context ctx) {
+    expression = x;
+    return false;
+  }
+
+  @Override
+  public boolean visit(JThisRef x, Context ctx) {
+    expression = program.getExprThisRef(x.getSourceInfo(), x.getClassType());
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index 0f084e2..27268b3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -38,6 +38,7 @@
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodCall;
 import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNode;
 import com.google.gwt.dev.jjs.ast.JPrefixOperation;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
@@ -70,6 +71,11 @@
   public class DeadCodeVisitor extends JModVisitor {
 
     /**
+     * An expression whose result does not matter.
+     */
+    private JExpression ignoringExpressionOutput;
+
+    /**
      * Short circuit binary operations.
      */
     @Override
@@ -294,7 +300,11 @@
      */
     @Override
     public void endVisit(JMultiExpression x, Context ctx) {
-      for (int i = 0; i < x.exprs.size() - 1; i++) {
+      /*
+       * If we're ignoring the output of this expression, we don't need to
+       * maintain the final multi value.
+       */
+      for (int i = 0; i < numRemovableExpressions(x); i++) {
         JExpression expr = x.exprs.get(i);
         if (!expr.hasSideEffects()) {
           x.exprs.remove(i);
@@ -302,6 +312,14 @@
           continue;
         }
 
+        // Remove nested JMultiExpressions
+        if (expr instanceof JMultiExpression) {
+          x.exprs.remove(i);
+          x.exprs.addAll(i, ((JMultiExpression) expr).exprs);
+          i--;
+          continue;
+        }
+
         if (expr instanceof JFieldRef) {
           JFieldRef fieldRef = (JFieldRef) expr;
           JExpression instance = fieldRef.getInstance();
@@ -449,6 +467,12 @@
       }
     }
 
+    @Override
+    public boolean visit(JExpressionStatement x, Context ctx) {
+      ignoringExpressionOutput = x.getExpr();
+      return true;
+    }
+
     private boolean hasNoDefaultCase(JSwitchStatement x) {
       JBlock body = x.getBody();
       boolean inDefault = false;
@@ -498,6 +522,16 @@
       return typeClassMap.get(type);
     }
 
+    private int numRemovableExpressions(JMultiExpression x) {
+      if (x == ignoringExpressionOutput) {
+        // All expressions can be removed.
+        return x.exprs.size();
+      } else {
+        // The last expression cannot be removed.
+        return x.exprs.size() - 1;
+      }
+    }
+
     /**
      * Removes any break statements that appear one after another.
      */
@@ -737,7 +771,11 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new DeadCodeElimination(program).execImpl();
+    return new DeadCodeElimination(program).execImpl(program);
+  }
+
+  public static boolean exec(JProgram program, JNode node) {
+    return new DeadCodeElimination(program).execImpl(node);
   }
 
   private final JProgram program;
@@ -758,11 +796,11 @@
     typeClassMap.put(program.getTypePrimitiveShort(), short.class);
   }
 
-  private boolean execImpl() {
+  private boolean execImpl(JNode node) {
     boolean madeChanges = false;
     while (true) {
       DeadCodeVisitor deadCodeVisitor = new DeadCodeVisitor();
-      deadCodeVisitor.accept(program);
+      deadCodeVisitor.accept(node);
       if (!deadCodeVisitor.didChange()) {
         break;
       }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ExpressionAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ExpressionAnalyzer.java
new file mode 100644
index 0000000..8450eb2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ExpressionAnalyzer.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JArrayRef;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JCastOperation;
+import com.google.gwt.dev.jjs.ast.JConditional;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JIntLiteral;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JNewArray;
+import com.google.gwt.dev.jjs.ast.JNewInstance;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JPostfixOperation;
+import com.google.gwt.dev.jjs.ast.JPrefixOperation;
+import com.google.gwt.dev.jjs.ast.JThisRef;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+
+/**
+ * Analyzes an expression and make a number of static analysis flags available
+ * based on the information available solely through the expression.
+ * 
+ * TODO: make this even smarter when we have real null analysis.
+ */
+public class ExpressionAnalyzer extends JVisitor {
+  private boolean accessesField;
+  private boolean accessesLocal;
+  private boolean accessesParameter;
+  private boolean assignmentToField;
+  private boolean assignmentToLocal;
+  private boolean assignmentToParameter;
+  private boolean canThrowException;
+  private boolean createsObject;
+  private int inConditional;
+
+  /**
+   * Does this expression read or write fields within the scope of the
+   * expression?
+   */
+  public boolean accessesField() {
+    return accessesField;
+  }
+
+  /**
+   * Does this expression read or write locals within the scope of the
+   * expression?
+   */
+  public boolean accessesLocal() {
+    return accessesLocal;
+  }
+
+  /**
+   * Does this expression read or write parameters within the scope of the
+   * expression?
+   */
+  public boolean accessesParameter() {
+    return accessesParameter;
+  }
+
+  public boolean canThrowException() {
+    return canThrowException;
+  }
+
+  public boolean createsObject() {
+    return createsObject;
+  }
+
+  @Override
+  public void endVisit(JArrayRef x, Context ctx) {
+    /*
+     * In Java, array references can throw IndexOutOfBoundsExceptions, but this
+     * isn't the case for current GWT generated code. If we add a strict array
+     * bounds check later, this flag would need to reflect it.
+     */
+
+    // If JArrayRef is null, this can throw a NullPointerException.
+    canThrowException = true;
+  }
+
+  @Override
+  public void endVisit(JBinaryOperation x, Context ctx) {
+    if (x.isAssignment()) {
+      JExpression lhs = x.getLhs();
+      if (lhs instanceof JArrayRef) {
+        // Array store operations can throw ArrayStoreExceptions
+        canThrowException = true;
+      } else {
+        analyzeStore(lhs);
+      }
+    }
+  }
+
+  @Override
+  public void endVisit(JCastOperation x, Context ctx) {
+    // Can throw ClassCastException
+    canThrowException = true;
+  }
+
+  @Override
+  public void endVisit(JFieldRef x, Context ctx) {
+    accessesField = true;
+
+    JExpression instance = x.getInstance();
+
+    // Field references using this are always safe
+    if (instance instanceof JThisRef) {
+      return;
+    }
+
+    /*
+     * We don't have enough information at this point to determine that a field
+     * reference is guaranteed to be safe.
+     * 
+     * If a field reference is static, it can throw exceptions via a clinit().
+     * 
+     * If a field is not static and isn't accessed using "this", the instance
+     * expression may be null.
+     */
+    canThrowException = true;
+  }
+
+  @Override
+  public void endVisit(JLocalRef x, Context ctx) {
+    accessesLocal = true;
+  }
+
+  @Override
+  public void endVisit(JMethodCall x, Context ctx) {
+    /*
+     * We can't assume anything about method calls right now, except that it
+     * can't assign to one of our locals or one of our parameters. It's possible
+     * that it could read from a field, assign to a field or throw an exception
+     * that we can't see.
+     */
+    assignmentToField = true;
+    accessesField = true;
+    canThrowException = true;
+  }
+
+  @Override
+  public void endVisit(JNewArray x, Context ctx) {
+    /*
+     * If no array bounds, the new array is being automatically initialized. If
+     * there are side-effects, they'll show up when we visit the initializers.
+     */
+    if (x.dims == null) {
+      return;
+    }
+
+    /*
+     * Can throw NegativeArraySizeException if we initialize an array with
+     * negative dimensions.
+     */
+    for (JExpression expression : x.dims) {
+      if (expression instanceof JIntLiteral) {
+        int value = ((JIntLiteral) expression).getValue();
+        if (value >= 0) {
+          continue;
+        }
+      }
+      canThrowException = true;
+    }
+  }
+
+  @Override
+  public void endVisit(JNewInstance x, Context ctx) {
+    /*
+     * Due to implementation details, a new instance operation has no other
+     * possible side-effects.
+     */
+    createsObject = true;
+  }
+
+  @Override
+  public void endVisit(JParameterRef x, Context ctx) {
+    accessesParameter = true;
+  }
+
+  @Override
+  public void endVisit(JPostfixOperation x, Context ctx) {
+    // Unary operations that are modifying cause assignment side-effects.
+    if (x.getOp().isModifying()) {
+      analyzeStore(x.getArg());
+    }
+  }
+
+  @Override
+  public void endVisit(JPrefixOperation x, Context ctx) {
+    // Unary operations that are modifying cause assignment side-effects.
+    if (x.getOp().isModifying()) {
+      analyzeStore(x.getArg());
+    }
+  }
+
+  /**
+   * Does this expression make assignments to variables within the scope of the
+   * expression?
+   */
+  public boolean hasAssignment() {
+    return assignmentToField || assignmentToLocal || assignmentToParameter;
+  }
+
+  /**
+   * Does this expression make assignments to fields within the scope of the
+   * expression?
+   */
+  public boolean hasAssignmentToField() {
+    return assignmentToField;
+  }
+
+  /**
+   * Does this expression make assignments to locals within the scope of the
+   * expression?
+   */
+  public boolean hasAssignmentToLocal() {
+    return assignmentToLocal;
+  }
+
+  /**
+   * Does this expression make assignments to parameters within the scope of the
+   * expression?
+   */
+  public boolean hasAssignmentToParameter() {
+    return assignmentToParameter;
+  }
+
+  @Override
+  public boolean visit(JBinaryOperation x, Context ctx) {
+    if (x.getOp() == JBinaryOperator.AND || x.getOp() == JBinaryOperator.OR) {
+      accept(x.getLhs());
+      inConditional++;
+      accept(x.getRhs());
+      inConditional--;
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public boolean visit(JConditional x, Context ctx) {
+    accept(x.getIfTest());
+    inConditional++;
+    accept(x.getThenExpr());
+    accept(x.getElseExpr());
+    inConditional--;
+
+    return false;
+  }
+
+  /**
+   * Determined if the current expression conditionally executes, based on its
+   * parent expressions.
+   */
+  protected boolean isInConditional() {
+    return inConditional > 0;
+  }
+
+  private void analyzeStore(JExpression expr) {
+    if (expr instanceof JFieldRef) {
+      assignmentToField = true;
+    } else if (expr instanceof JParameterRef) {
+      assignmentToParameter = true;
+    } else if (expr instanceof JLocalRef) {
+      assignmentToLocal = true;
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
index 24158dd..941ac34 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
@@ -15,14 +15,11 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
-import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.ast.Context;
-import com.google.gwt.dev.jjs.ast.JBlock;
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JExpressionStatement;
-import com.google.gwt.dev.jjs.ast.JField;
-import com.google.gwt.dev.jjs.ast.JFieldRef;
-import com.google.gwt.dev.jjs.ast.JLiteral;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodBody;
 import com.google.gwt.dev.jjs.ast.JMethodCall;
@@ -33,73 +30,84 @@
 import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JReturnStatement;
 import com.google.gwt.dev.jjs.ast.JStatement;
-import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.JThisRef;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Inline methods that can be inlined.
+ * Inline methods that can be inlined. The current implementation limits the
+ * methods that can be inlined to those that are composed of at most two
+ * top-level expressions.
  * 
- * TODO(later): more aggressive inlining
+ * Future improvements will allow more complex methods to be inlined based on
+ * the number of call sites, as well as adding support for more complex target
+ * method expressions.
  */
 public class MethodInliner {
 
   /**
-   * Flattens <code>JMultiExpressions</code> where possible.
-   * 
-   * TODO: make this a JModVisitor
+   * Clones an expression, ensuring no local or this refs.
    */
-  public class FlattenMultiVisitor extends JVisitor {
+  private class CloneCalleeExpressionVisitor extends CloneExpressionVisitor {
 
-    private boolean didChange = false;
-
-    @Override
-    public boolean didChange() {
-      return didChange;
+    public CloneCalleeExpressionVisitor() {
+      super(program);
     }
 
     @Override
-    public void endVisit(JMultiExpression x, Context ctx) {
-      ArrayList<JExpression> exprs = x.exprs;
+    public boolean visit(JLocalRef x, Context ctx) {
+      throw new InternalCompilerException(
+          "Unable to clone a local reference in a function being inlined");
+    }
 
-      /*
-       * Add the contents of all nested multis into the top multi, in place. We
-       * are in fact iterating over nodes we've just added, but that should be
-       * okay as the children will already be flattened.
-       */
-      for (int i = 0; i < exprs.size(); ++i) {
-        JExpression expr = exprs.get(i);
-        if (expr instanceof JMultiExpression) {
-          JMultiExpression sub = (JMultiExpression) expr;
-          exprs.addAll(i + 1, sub.exprs);
-          didChange = true;
-        }
-      }
-
-      // now remove the old multis
-      for (Iterator<JExpression> it = exprs.iterator(); it.hasNext();) {
-        JExpression expr = it.next();
-        if (expr instanceof JMultiExpression) {
-          it.remove();
-          didChange = true;
-        }
-      }
+    @Override
+    public boolean visit(JThisRef x, Context ctx) {
+      throw new InternalCompilerException("Should not encounter a JThisRef "
+          + "within a static method");
     }
   }
+
+  /**
+   * Replace parameters inside an inlined expression with arguments to the
+   * inlined method.
+   */
+  private class ParameterReplacer extends JModVisitor {
+    private final JMethodCall methodCall;
+
+    public ParameterReplacer(JMethodCall methodCall) {
+      this.methodCall = methodCall;
+    }
+
+    @Override
+    public void endVisit(JParameterRef x, Context ctx) {
+      int paramIndex = methodCall.getTarget().params.indexOf(x.getParameter());
+      assert paramIndex != -1;
+
+      // Replace with a cloned call argument.
+      CloneExpressionVisitor cloner = new CloneExpressionVisitor(program);
+      JExpression arg = methodCall.getArgs().get(paramIndex);
+      JExpression clone = cloner.cloneExpression(arg);
+      ctx.replaceMe(clone);
+    }
+  }
+
   /**
    * Method inlining visitor.
    */
-  public class InliningVisitor extends JModVisitor {
+  private class InliningVisitor extends JModVisitor {
+
+    protected final Set<JMethod> modifiedMethods = new HashSet<JMethod>();
+
     /**
      * Resets with each new visitor, which is good since things that couldn't be
-     * inlined before might become inlineable.
+     * inlined before might become inlinable.
      */
-    Set<JMethod> cannotInline = new HashSet<JMethod>();
+    private final Set<JMethod> cannotInline = new HashSet<JMethod>();
+    private JExpression ignoringReturnValueFor;
 
     @Override
     public void endVisit(JMethod x, Context ctx) {
@@ -110,8 +118,8 @@
     public void endVisit(JMethodCall x, Context ctx) {
       JMethod method = x.getTarget();
 
-      // The method call must be known statically.
-      if (!method.isStatic() || method.isNative()) {
+      if (currentMethod == method) {
+        // Never try to inline a recursive call!
         return;
       }
 
@@ -119,289 +127,332 @@
         return;
       }
 
-      JMethodBody body = (JMethodBody) method.getBody();
-      List<JStatement> stmts = body.getStatements();
       boolean possibleToInline = false;
 
-      // clinit() calls cannot be inlined unless they are empty
-      if (method.getEnclosingType() != null
-          && method.getEnclosingType().methods.get(0) == method
-          && !stmts.isEmpty()) {
-        return;
-      }
+      if (method.isStatic() && !method.isNative()) {
+        JMethodBody body = (JMethodBody) method.getBody();
+        List<JStatement> stmts = body.getStatements();
 
-      if (stmts.isEmpty()) {
-        tryInlineEmptyMethodCall(x, ctx);
-        possibleToInline = true;
-      } else if (stmts.size() == 1) {
-        JStatement stmt = stmts.get(0);
-        if (stmt instanceof JReturnStatement) {
-          possibleToInline = tryInlineExpression(x, ctx,
-              ((JReturnStatement) stmt).getExpr());
-        } else if (stmt instanceof JExpressionStatement) {
-          possibleToInline = tryInlineExpression(x, ctx,
-              ((JExpressionStatement) stmt).getExpr());
+        if (method.getEnclosingType() != null
+            && method.getEnclosingType().methods.get(0) == method
+            && !stmts.isEmpty()) {
+          // clinit() calls cannot be inlined unless they are empty
+          possibleToInline = false;
+        } else {
+          JMultiExpression multi = createMultiExpressionFromBody(body,
+              ignoringReturnValueFor == x);
+          if (multi != null) {
+            possibleToInline = tryInlineExpression(x, ctx, multi);
+          }
         }
       }
 
+      // If it will never be possible to inline the method, add it to a
+      // blacklist
       if (!possibleToInline) {
         cannotInline.add(method);
       }
     }
 
     @Override
+    public boolean visit(JExpressionStatement x, Context ctx) {
+      ignoringReturnValueFor = x.getExpr();
+      return true;
+    }
+
+    @Override
     public boolean visit(JMethod x, Context ctx) {
       currentMethod = x;
       return true;
     }
 
-    /**
-     * This complicated method has a simple purpose: see if the expression part
-     * of a return statement is inlinable. The trickiness comes from the fact
-     * that we'd like to be able to do this recursively in certain cases. For
-     * example, the accessor method
-     * 
-     * <pre>
-     * $getFoo(this$static) {
-     *  return this$static.foo
-     * }
-     * </pre>
-     * 
-     * should be inlinable, but we have to first examine the field reference and
-     * then recursively determine that the qualifier is inlinable.
-     */
-    private JExpression canInlineExpression(SourceInfo info,
-        JExpression targetExpr, List<JParameter> params,
-        ArrayList<JExpression> args, int[] magicArg) {
-      if (targetExpr instanceof JLiteral) {
-        // just reference the same JLiteral
-        /*
-         * hackish: pretend there is an arg that is returned which comes after
-         * all the real args; this allows the evaluation order check in
-         * tryInlineSimpleMethodCall to succeed
-         */
-        magicArg[0] = args.size();
-        return targetExpr;
-      } else if (targetExpr instanceof JParameterRef) {
-        // translate the param ref into the appropriate arg
-        int i = params.indexOf(((JParameterRef) targetExpr).getTarget());
-        assert (i >= 0);
-        magicArg[0] = i;
-        return args.get(i);
-      } else if (targetExpr instanceof JFieldRef) {
-        JFieldRef oldFieldRef = (JFieldRef) targetExpr;
-        JField field = oldFieldRef.getField();
-        JExpression instance = oldFieldRef.getInstance();
-        if (instance != null) {
-          // If an instance field, we have to be able to inline the qualifier
-          instance = canInlineExpression(info, instance, params, args, magicArg);
-          if (instance == null) {
-            return null;
-          }
-        }
-        JFieldRef newFieldRef = new JFieldRef(program, info, instance, field,
-            currentMethod.getEnclosingType());
-        return newFieldRef;
-      } else {
-        /*
-         * For now, only inline REALLY trivial stuff since we have no way of
-         * cloning arbitrary expressions.
-         */
-        return null;
-      }
-    }
-
-    /**
-     * Returns <code>true</code> if inlining the target expression would
-     * eliminate a necessary clinit.
-     */
-    private boolean checkClinitViolation(JMethodCall x,
-        JExpression resultExpression) {
+    private JMethodCall createClinitCall(JMethodCall x) {
       JReferenceType targetEnclosingType = x.getTarget().getEnclosingType();
       if (!program.typeOracle.checkClinit(currentMethod.getEnclosingType(),
           targetEnclosingType)) {
         // Access from this class to the target class won't trigger a clinit
-        return false;
+        return null;
       }
       if (program.isStaticImpl(x.getTarget())) {
         // No clinit needed; target is really an instance method.
+        return null;
+      }
+      if (x.getTarget() == x.getTarget().getEnclosingType().methods.get(0)) {
+        // This is a clinit call, doesn't need another clinit
+        return null;
+      }
+
+      JMethod clinit = targetEnclosingType.methods.get(0);
+
+      // If the clinit is a non-native, empty body we can optimize it out here
+      if (!clinit.isNative()
+          && (((JMethodBody) clinit.getBody())).getStatements().size() == 0) {
+        return null;
+      }
+
+      return new JMethodCall(program, x.getSourceInfo(), null, clinit);
+    }
+
+    /**
+     * Creates a multi expression for evaluating a method call instance and
+     * possible clinit. This is a precursor for inlining the remainder of a
+     * method.
+     */
+    private JMultiExpression createMultiExpressionForInstanceAndClinit(
+        JMethodCall x) {
+      JMultiExpression multi = new JMultiExpression(program, x.getSourceInfo());
+
+      // Any instance expression goes first (this can happen even with statics).
+      if (x.getInstance() != null) {
+        multi.exprs.add(x.getInstance());
+      }
+
+      // If we need a clinit call, add it first
+      JMethodCall clinit = createClinitCall(x);
+      if (clinit != null) {
+        multi.exprs.add(clinit);
+      }
+      return multi;
+    }
+
+    /**
+     * Creates a JMultiExpression from a set of JExpressionStatements,
+     * optionally terminated by a JReturnStatement. If the method doesn't match
+     * this pattern, it returns <code>null</code>.
+     * 
+     * If a method has a non-void return statement and can be represented as a
+     * multi-expression, the output of the multi-expression will be the return
+     * expression of the method. If the method is void, the output of the
+     * multi-expression should be considered undefined.
+     */
+    private JMultiExpression createMultiExpressionFromBody(JMethodBody body,
+        boolean ignoringReturnValue) {
+      JMultiExpression multi = new JMultiExpression(program,
+          body.getSourceInfo());
+      CloneCalleeExpressionVisitor cloner = new CloneCalleeExpressionVisitor();
+
+      for (JStatement stmt : body.getStatements()) {
+        if (stmt instanceof JExpressionStatement) {
+          JExpressionStatement exprStmt = (JExpressionStatement) stmt;
+          JExpression expr = exprStmt.getExpr();
+          JExpression clone = cloner.cloneExpression(expr);
+          multi.exprs.add(clone);
+        } else if (stmt instanceof JReturnStatement) {
+          JReturnStatement returnStatement = (JReturnStatement) stmt;
+          JExpression expr = returnStatement.getExpr();
+          if (expr != null) {
+            if (!ignoringReturnValue || expr.hasSideEffects()) {
+              JExpression clone = cloner.cloneExpression(expr);
+              multi.exprs.add(clone);
+            }
+          }
+          // We hit an unconditional return; no need to evaluate anything else.
+          break;
+        } else {
+          // Any other kind of statement won't be inlinable.
+          return null;
+        }
+      }
+
+      return multi;
+    }
+
+    /**
+     * Creates a multi expression for evaluating a method call instance,
+     * possible clinit, and all arguments. This is a precursor for inlining the
+     * remainder of a method that does not reference any parameters.
+     */
+    private JMultiExpression createMultiExpressionIncludingArgs(JMethodCall x) {
+      JMultiExpression multi = createMultiExpressionForInstanceAndClinit(x);
+
+      for (int i = 0, c = x.getArgs().size(); i < c; ++i) {
+        JExpression arg = x.getArgs().get(i);
+        ExpressionAnalyzer analyzer = new ExpressionAnalyzer();
+        analyzer.accept(arg);
+
+        if (analyzer.hasAssignment() || analyzer.canThrowException()) {
+          multi.exprs.add(arg);
+        }
+      }
+      return multi;
+    }
+
+    /**
+     * Replace the current expression with a given multi-expression and mark the
+     * method as modified. The dead-code elimination pass will optimize this if
+     * necessary.
+     */
+    private void replaceWithMulti(Context ctx, JMultiExpression multi) {
+      ctx.replaceMe(multi);
+      modifiedMethods.add(currentMethod);
+    }
+
+    /**
+     * Inline a call to an expression.
+     */
+    private boolean tryInlineExpression(JMethodCall x, Context ctx,
+        JMultiExpression targetExpr) {
+      List<JParameter> params = x.getTarget().params;
+      ArrayList<JExpression> args = x.getArgs();
+
+      /*
+       * Limit inlined methods to multiexpressions of length 2 for now. This
+       * handles the simple { return JVariableRef; } or { expression; return
+       * something; } cases.
+       * 
+       * TODO: add an expression complexity analyzer.
+       */
+      if (targetExpr.exprs.size() > 2) {
+        return false;
+      }
+
+      // Do not inline anything that modifies one of its params.
+      ExpressionAnalyzer targetAnalyzer = new ExpressionAnalyzer();
+      targetAnalyzer.accept(targetExpr);
+      if (targetAnalyzer.hasAssignmentToParameter()) {
         return false;
       }
 
       /*
-       * Potential clinit violation! We can only allow this if the result is
-       * itself a static field ref into the target class, which would trigger
-       * the clinit itself.
+       * After this point, it's possible that the method might be inlinable at
+       * some call sites, depending on its arguments. From here on return 'true'
+       * as the method might be inlinable elsewhere.
        */
 
-      // resultExpression might be null, which behaves correctly here.
-      if (!(resultExpression instanceof JFieldRef)) {
-        return true;
-      }
-      JFieldRef fieldRefResult = (JFieldRef) resultExpression;
-      JField fieldResult = fieldRefResult.getField();
-      if (!fieldResult.isStatic()) {
-        // A nonstatic field reference won't trigger the clinit we need.
-        return true;
-      }
-      if (fieldResult.getEnclosingType() != targetEnclosingType) {
-        // We have a static field reference, but it's to the wrong class (not
-        // the class whose clinit we must trigger).
-        return true;
-      }
-      // The correct cross-class static field reference will trigger the clinit.
-      return false;
-    }
-
-    /**
-     * Inlines a call to an empty method.
-     */
-    private void tryInlineEmptyMethodCall(JMethodCall x, Context ctx) {
-      if (checkClinitViolation(x, null)) {
-        return;
-      }
-      JMultiExpression multi = new JMultiExpression(program, x.getSourceInfo());
-      JExpression instance = x.getInstance();
-      if (instance != null && instance.hasSideEffects()) {
-        multi.exprs.add(x.getInstance());
-      }
-      for (int i = 0, c = x.getArgs().size(); i < c; ++i) {
-        if (x.getArgs().get(i).hasSideEffects()) {
-          multi.exprs.add(x.getArgs().get(i));
-        }
-      }
-      ctx.replaceMe(multi);
-    }
-
-    /**
-     * Inline a call to a method that contains only a return statement.
-     */
-    private boolean tryInlineExpression(JMethodCall x, Context ctx,
-        JExpression targetExpr) {
-      List<JParameter> params = x.getTarget().params;
-      ArrayList<JExpression> args = x.getArgs();
-
-      // the expression returned by the inlined method, if any
-      JExpression resultExpression;
-      // the argument that is returned by the inlined method, if any
-      int magicArg[] = new int[1];
-
-      resultExpression = canInlineExpression(x.getSourceInfo(), targetExpr,
-          params, args, magicArg);
-
-      if (resultExpression == null) {
-        return false; // cannot inline
-      }
-
-      // The expression is inlinable.
-
-      if (checkClinitViolation(x, resultExpression)) {
-        /*
-         * Inlining here would cause a clinit to not fire, so we can't. But
-         * return true, because this method could still be inlined from within
-         * the same class.
-         */
+      /*
+       * There are a different number of parameters than args - this is likely a
+       * result of parameter pruning. Don't consider this call site a candidate.
+       * 
+       * TODO: would this be possible in the trivial delegation case?
+       */
+      if (params.size() != args.size()) {
         return true;
       }
 
-      // the argument that is returned by the inlined method
-      int iMagicArg = magicArg[0];
+      // Run the order check. This verifies that all the parameters are
+      // referenced once and only once, not within a conditionally-executing
+      // expression and before any tricky target expressions, such as:
+      // - assignments to any variable
+      // - expressions that throw exceptions
+      // - field references
 
-      JMultiExpression multi = new JMultiExpression(program, x.getSourceInfo());
+      /*
+       * Ensure correct evaluation order or params relative to each other and to
+       * other expressions.
+       */
+      OrderVisitor orderVisitor = new OrderVisitor(x.getTarget().params);
+      orderVisitor.accept(targetExpr);
 
-      // Evaluate the instance argument (we can have one even with static calls)
-      JExpression instance = x.getInstance();
-      if (instance != null && instance.hasSideEffects()) {
-        multi.exprs.add(x.getInstance());
+      /*
+       * A method that doesn't touch any parameters is trivially inlinable (this
+       * covers the empty method case)
+       */
+      if (orderVisitor.checkResults() == SideEffectCheck.NO_REFERENCES) {
+        JMultiExpression multi = createMultiExpressionIncludingArgs(x);
+        multi.exprs.add(targetExpr);
+        replaceWithMulti(ctx, multi);
+        return true;
       }
 
-      // Now evaluate any side-effect args that aren't the magic arg.
-      for (int i = 0; i < params.size(); ++i) {
-        if (args.get(i).hasSideEffects()) {
-          if (i < iMagicArg) {
-            // evaluate this arg inside of the multi
-            multi.exprs.add(args.get(i));
-          } else if (i == iMagicArg) {
-            // skip this arg, we'll do it below as the final one
-          } else {
-            assert (i > iMagicArg);
+      /*
+       * We can still inline in the case where all of the actual arguments are
+       * "safe". They must have no side effects, and also have values which
+       * could not be affected by the execution of any code within the callee.
+       */
+      if (orderVisitor.checkResults() == SideEffectCheck.FAILS) {
+        for (JExpression arg : x.getArgs()) {
+          ExpressionAnalyzer argAnalyzer = new ExpressionAnalyzer();
+          argAnalyzer.accept(arg);
+
+          if (argAnalyzer.hasAssignment() || argAnalyzer.accessesField()
+              || argAnalyzer.createsObject() || argAnalyzer.canThrowException()) {
+
             /*
-             * ABORT ABORT ABORT! This would cause an out-of-order evalutation.
-             * Due to the way we construct multis, the magic arg must come last.
-             * However, we've encountered a case where an argument coming after
-             * the magic arg must be evaluated. Just bail.
-             * 
-             * However, we return true because this call might be inlinable at
-             * other call sites.
+             * This argument evaluation could affect or be affected by the
+             * callee so we cannot inline here.
              */
             return true;
           }
         }
       }
 
-      // add in the result expression as the last item in the multi
-      multi.exprs.add(resultExpression);
-      ctx.replaceMe(multi);
+      // We're safe to inline.
+      JMultiExpression multi = createMultiExpressionForInstanceAndClinit(x);
+
+      // Replace all params in the target expression with the actual arguments.
+      new ParameterReplacer(x).accept(targetExpr);
+
+      multi.exprs.add(targetExpr);
+      replaceWithMulti(ctx, multi);
       return true;
     }
   }
 
   /**
-   * Reduces <code>JMultiExpression</code> where possible.
+   * Verifies that all the parameters are referenced once and only once, not
+   * within a conditionally-executing expression, and any before trouble some
+   * expressions evaluate. Examples of troublesome expressions include:
+   * 
+   * <ul>
+   * <li>assignments to any variable</li>
+   * <li>expressions that throw exceptions</li>
+   * <li>field references</li>
+   * </ul>
    */
-  public class ReduceMultiVisitor extends JModVisitor {
+  private class OrderVisitor extends ExpressionAnalyzer {
+    private int currentIndex = 0;
+    private final List<JParameter> parameters;
+    private boolean succeeded = true;
 
-    @Override
-    public void endVisit(JBlock x, Context ctx) {
-      for (Iterator<JStatement> it = x.statements.iterator(); it.hasNext();) {
-        JStatement stmt = it.next();
-        // If we're a JExprStmt with no side effects, just remove me
-        if (stmt instanceof JExpressionStatement) {
-          JExpression expr = ((JExpressionStatement) stmt).getExpr();
-          if (!expr.hasSideEffects()) {
-            it.remove();
-            didChange = true;
-          }
-        }
+    public OrderVisitor(List<JParameter> parameters) {
+      this.parameters = parameters;
+    }
+
+    public SideEffectCheck checkResults() {
+      if (succeeded && currentIndex == parameters.size()) {
+        return SideEffectCheck.CORRECT_ORDER;
       }
+
+      if (succeeded && currentIndex == 0) {
+        return SideEffectCheck.NO_REFERENCES;
+      }
+
+      return SideEffectCheck.FAILS;
     }
 
     @Override
-    public void endVisit(JMultiExpression x, Context ctx) {
-      ArrayList<JExpression> exprs = x.exprs;
+    public void endVisit(JParameterRef x, Context ctx) {
+      JParameter param = x.getParameter();
 
-      final int c = exprs.size();
-      if (c == 0) {
-        return;
+      // If the expression has side-effects before a parameter reference, fail
+      if (hasAssignment() || accessesField() || canThrowException()) {
+        succeeded = false;
       }
 
-      int countSideEffectsBeforeLast = 0;
-      for (int i = 0; i < c - 1; ++i) {
-        JExpression expr = exprs.get(i);
-        if (expr.hasSideEffects()) {
-          ++countSideEffectsBeforeLast;
-        }
+      // If this parameter reference won't always execute, fail
+      if (isInConditional()) {
+        succeeded = false;
       }
 
-      if (countSideEffectsBeforeLast == 0) {
-        ctx.replaceMe(x.exprs.get(c - 1));
+      // Ensure this parameter is evaluated in the correct order relative to
+      // other parameters.
+      if (parameters.indexOf(param) == currentIndex) {
+        currentIndex++;
       } else {
-        JMultiExpression newMulti = new JMultiExpression(program,
-            x.getSourceInfo());
-        for (int i = 0; i < c - 1; ++i) {
-          JExpression expr = exprs.get(i);
-          if (expr.hasSideEffects()) {
-            newMulti.exprs.add(expr);
-          }
-        }
-        newMulti.exprs.add(x.exprs.get(c - 1));
-        if (newMulti.exprs.size() < x.exprs.size()) {
-          ctx.replaceMe(newMulti);
-        }
+        succeeded = false;
       }
+
+      super.endVisit(x, ctx);
     }
   }
 
+  /**
+   * Results of a side-effect and order check.
+   */
+  private enum SideEffectCheck {
+    CORRECT_ORDER, FAILS, NO_REFERENCES
+  }
+
   public static boolean exec(JProgram program) {
     return new MethodInliner(program).execImpl();
   }
@@ -423,13 +474,11 @@
       }
       madeChanges = true;
 
-      FlattenMultiVisitor flattener = new FlattenMultiVisitor();
-      flattener.accept(program);
-
-      ReduceMultiVisitor reducer = new ReduceMultiVisitor();
-      reducer.accept(program);
+      // Run a cleanup on the methods we just modified
+      for (JMethod method : inliner.modifiedMethods) {
+        DeadCodeElimination.exec(program, method);
+      }
     }
     return madeChanges;
   }
-
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/MethodCallTest.java b/user/test/com/google/gwt/dev/jjs/test/MethodCallTest.java
index 70f7a43..d7a84d3 100644
--- a/user/test/com/google/gwt/dev/jjs/test/MethodCallTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/MethodCallTest.java
@@ -18,7 +18,7 @@
 import com.google.gwt.junit.client.GWTTestCase;
 
 /**
- * TODO: document me.
+ * Tests method invocations including potential inlining bugs.
  */
 public class MethodCallTest extends GWTTestCase {
 
@@ -140,6 +140,18 @@
     assertEquals(10, result);
   }
 
+  public void testAssignsToParam() {
+    value = 10;
+    int result = assignsToParam(value);
+    assertEquals(10, value);
+    assertEquals(11, result);
+
+    int localValue = 20;
+    result = assignsToParam(localValue);
+    assertEquals(20, localValue);
+    assertEquals(21, result);
+  }
+
   public void testManyArgs() {
     assertEquals(32385, manyArgs(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
         14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
@@ -160,6 +172,15 @@
         243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254));
   }
 
+  /**
+   * Ensure that we correctly inline a method that ignores its parameters.
+   */
+  public void testNoParameterRefs() {
+    int value = 100;
+    assertEquals(1, ignoreParams(value++, value++));
+    assertEquals(102, value);
+  }
+
   public void testRecursion() {
     assertEquals(20100, recursiveSum(200));
   }
@@ -293,6 +314,10 @@
     return value + i;
   }
 
+  private int assignsToParam(int x) {
+    return ++x;
+  }
+
   private int checkOrder(int x, int y) {
     return y * 1000 + x;
   }
@@ -313,6 +338,10 @@
     return (value *= 2) + i;
   }
 
+  private int ignoreParams(int i, int j) {
+    return 1;
+  }
+
   private int recursiveSum(int x) {
     if (x == 0) {
       return 0;