Merge "Take into account catch handlers when adding item-based string."
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 5e9e0f2..997a8c9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.List;
 
-abstract class FieldInstruction extends Instruction {
+public abstract class FieldInstruction extends Instruction {
 
   protected final MemberType type;
   protected final DexField field;
@@ -41,6 +41,16 @@
     return field;
   }
 
+  @Override
+  public boolean isFieldInstruction() {
+    return true;
+  }
+
+  @Override
+  public FieldInstruction asFieldInstruction() {
+    return this;
+  }
+
   /**
    * Returns the target of this field instruction, if such target is known, or null.
    * <p>
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 68253c6..cdca04e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -582,6 +582,14 @@
     return null;
   }
 
+  public boolean isFieldInstruction() {
+    return false;
+  }
+
+  public FieldInstruction asFieldInstruction() {
+    return null;
+  }
+
   public boolean isGoto() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index c90f1cf..f6e087b 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -17,9 +17,11 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
@@ -32,6 +34,7 @@
 import com.google.common.collect.Streams;
 import java.util.Arrays;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -69,130 +72,192 @@
   }
 
   public void decoupleIdentifierNameStringsInMethod(DexEncodedMethod encodedMethod, IRCode code) {
-    for (BasicBlock block : code.blocks) {
+    ListIterator<BasicBlock> blocks = code.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
       InstructionListIterator iterator = block.listIterator();
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
-        if (instruction.isStaticPut()) {
-          StaticPut staticPut = instruction.asStaticPut();
-          DexField field = staticPut.getField();
-          if (identifierNameStrings.contains(field)) {
-            Value in = staticPut.inValue();
-            Value newIn = decoupleIdentifierIfNecessary(code, iterator, staticPut, in);
-            if (newIn != in) {
-              iterator.replaceCurrentInstruction(
-                  new StaticPut(staticPut.getType(), newIn, field));
-              encodedMethod.markUseIdentifierNameString();
-            }
+        // v_n <- "x.y.z" // in.definition
+        // ...
+        // ... <- ... v_n ..
+        // ...
+        // this.fld <- v_n // fieldPut
+        //
+        //   ~>
+        //
+        // ...
+        // v_n' <- DexItemBasedString("Lx/y/z;") // decoupled
+        // this.fld <- v_n' // fieldPut
+        if (instruction.isStaticPut() || instruction.isInstancePut()) {
+          FieldInstruction fieldPut = instruction.asFieldInstruction();
+          DexField field = fieldPut.getField();
+          if (!identifierNameStrings.contains(field)) {
+            continue;
           }
-        } else if (instruction.isInstancePut()) {
-          InstancePut instancePut = instruction.asInstancePut();
-          DexField field = instancePut.getField();
-          if (identifierNameStrings.contains(field)) {
-            Value in = instancePut.value();
-            Value newIn = decoupleIdentifierIfNecessary(code, iterator, instancePut, in);
-            if (newIn != in) {
-              iterator.replaceCurrentInstruction(
-                  new InstancePut(instancePut.getType(), field, instancePut.object(), newIn));
-              encodedMethod.markUseIdentifierNameString();
-            }
+          Value in = instruction.isStaticPut()
+              ? instruction.asStaticPut().inValue()
+              : instruction.asInstancePut().value();
+          if (!in.isConstString()) {
+            continue;
           }
+          DexString original = in.getConstInstruction().asConstString().getValue();
+          DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
+          if (itemBasedString == null) {
+            continue;
+          }
+          // Move the cursor back to $fieldPut
+          assert iterator.peekPrevious() == fieldPut;
+          iterator.previous();
+          // Prepare $decoupled just before $fieldPut
+          Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+          ConstString decoupled = new ConstString(newIn, itemBasedString);
+          decoupled.setPosition(fieldPut.getPosition());
+          // If the current block has catch handler, split into two blocks.
+          // Because const-string we're about to add is also a throwing instr, we need to split
+          // before adding it.
+          BasicBlock blockWithFieldInstruction =
+              block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
+          if (blockWithFieldInstruction != block) {
+            // If we split, add const-string at the end of the currently visiting block.
+            iterator = block.listIterator(block.getInstructions().size());
+            iterator.previous();
+            iterator.add(decoupled);
+            // Restore the cursor and block.
+            iterator = blockWithFieldInstruction.listIterator();
+            assert iterator.peekNext() == fieldPut;
+            iterator.next();
+            block = blockWithFieldInstruction;
+          } else {
+            // Otherwise, just add it to the current block at the position of the iterator.
+            iterator.add(decoupled);
+            // Restore the cursor.
+            assert iterator.peekNext() == fieldPut;
+            iterator.next();
+          }
+          if (instruction.isStaticPut()) {
+            StaticPut staticPut = instruction.asStaticPut();
+            iterator.replaceCurrentInstruction(
+                new StaticPut(staticPut.getType(), newIn, field));
+          } else {
+            assert instruction.isInstancePut();
+            InstancePut instancePut = instruction.asInstancePut();
+            iterator.replaceCurrentInstruction(
+                new InstancePut(instancePut.getType(), field, instancePut.object(), newIn));
+          }
+          encodedMethod.markUseIdentifierNameString();
         } else if (instruction.isInvokeMethod()) {
           InvokeMethod invoke = instruction.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
-          if (identifierNameStrings.contains(invokedMethod)) {
-            List<Value> ins = invoke.arguments();
-            Value[] changes = new Value [ins.size()];
-            if (isReflectionMethod(dexItemFactory, invokedMethod)) {
-              decoupleReflectiveMemberIdentifier(code, iterator, invoke, changes);
+          if (!identifierNameStrings.contains(invokedMethod)) {
+            continue;
+          }
+          List<Value> ins = invoke.arguments();
+          Value[] changes = new Value [ins.size()];
+          if (isReflectionMethod(dexItemFactory, invokedMethod)) {
+            DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
+            if (itemBasedString == null) {
+              continue;
+            }
+            DexType returnType = invoke.getReturnType();
+            boolean isClassForName =
+                returnType.descriptor == dexItemFactory.classDescriptor;
+            boolean isReferenceFieldUpdater =
+                returnType.descriptor == dexItemFactory.referenceFieldUpdaterDescriptor;
+            int positionOfIdentifier = isClassForName ? 0 : (isReferenceFieldUpdater ? 2 : 1);
+            Value in = invoke.arguments().get(positionOfIdentifier);
+            // Move the cursor back to $invoke
+            assert iterator.peekPrevious() == invoke;
+            iterator.previous();
+            // Prepare $decoupled just before $invoke
+            Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+            ConstString decoupled = new ConstString(newIn, itemBasedString);
+            decoupled.setPosition(invoke.getPosition());
+            changes[positionOfIdentifier] = newIn;
+            // If the current block has catch handler, split into two blocks.
+            // Because const-string we're about to add is also a throwing instr, we need to split
+            // before adding it.
+            BasicBlock blockWithInvoke =
+                block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
+            if (blockWithInvoke != block) {
+              // If we split, add const-string at the end of the currently visiting block.
+              iterator = block.listIterator(block.getInstructions().size());
+              iterator.previous();
+              iterator.add(decoupled);
+              // Restore the cursor and block.
+              iterator = blockWithInvoke.listIterator();
+              assert iterator.peekNext() == invoke;
+              iterator.next();
+              block = blockWithInvoke;
             } else {
-              for (int i = 0; i < ins.size(); i++) {
-                Value in = ins.get(i);
-                Value newIn = decoupleIdentifierIfNecessary(code, iterator, invoke, in);
-                if (newIn != in) {
-                  changes[i] = newIn;
-                }
+              // Otherwise, just add it to the current block at the position of the iterator.
+              iterator.add(decoupled);
+              // Restore the cursor.
+              assert iterator.peekNext() == invoke;
+              iterator.next();
+            }
+          } else {
+            // For general invoke. Multiple arguments can be string literals to be renamed.
+            for (int i = 0; i < ins.size(); i++) {
+              Value in = ins.get(i);
+              if (!in.isConstString()) {
+                continue;
+              }
+              DexString original = in.getConstInstruction().asConstString().getValue();
+              DexItemBasedString itemBasedString =
+                  inferMemberOrTypeFromNameString(appInfo, original);
+              if (itemBasedString == null) {
+                continue;
+              }
+              // Move the cursor back to $invoke
+              assert iterator.peekPrevious() == invoke;
+              iterator.previous();
+              // Prepare $decoupled just before $invoke
+              Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+              ConstString decoupled = new ConstString(newIn, itemBasedString);
+              decoupled.setPosition(invoke.getPosition());
+              changes[i] = newIn;
+              // If the current block has catch handler, split into two blocks.
+              // Because const-string we're about to add is also a throwing instr, we need to split
+              // before adding it.
+              BasicBlock blockWithInvoke =
+                  block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
+              if (blockWithInvoke != block) {
+                // If we split, add const-string at the end of the currently visiting block.
+                iterator = block.listIterator(block.getInstructions().size());
+                iterator.previous();
+                iterator.add(decoupled);
+                // Restore the cursor and block.
+                iterator = blockWithInvoke.listIterator();
+                assert iterator.peekNext() == invoke;
+                iterator.next();
+                block = blockWithInvoke;
+              } else {
+                // Otherwise, just add it to the current block at the position of the iterator.
+                iterator.add(decoupled);
+                // Restore the cursor.
+                assert iterator.peekNext() == invoke;
+                iterator.next();
               }
             }
-            if (!Arrays.stream(changes).allMatch(Objects::isNull)) {
-              List<Value> newIns =
-                  Streams.mapWithIndex(
-                      ins.stream(),
-                      (in, index) -> changes[(int) index] != null ? changes[(int) index] : in)
-                  .collect(Collectors.toList());
-              iterator.replaceCurrentInstruction(
-                  Invoke.create(
-                      invoke.getType(),
-                      invokedMethod,
-                      invokedMethod.proto,
-                      invoke.outValue(),
-                      newIns));
-              encodedMethod.markUseIdentifierNameString();
-            }
+          }
+          if (!Arrays.stream(changes).allMatch(Objects::isNull)) {
+            List<Value> newIns =
+                Streams.mapWithIndex(
+                    ins.stream(),
+                    (in, index) -> changes[(int) index] != null ? changes[(int) index] : in)
+                .collect(Collectors.toList());
+            iterator.replaceCurrentInstruction(
+                Invoke.create(
+                    invoke.getType(),
+                    invokedMethod,
+                    invokedMethod.proto,
+                    invoke.outValue(),
+                    newIns));
+            encodedMethod.markUseIdentifierNameString();
           }
         }
       }
     }
   }
-
-  private Value decoupleIdentifierIfNecessary(
-      IRCode code, InstructionListIterator iterator, Instruction base, Value in) {
-    if (!in.isConstString()) {
-      return in;
-    }
-    DexString original = in.getConstInstruction().asConstString().getValue();
-    DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
-    if (itemBasedString == null) {
-      return in;
-    }
-    return insertItemBasedString(code, iterator, base, in, itemBasedString);
-  }
-
-  private void decoupleReflectiveMemberIdentifier(
-      IRCode code, InstructionListIterator iterator, InvokeMethod invoke, Value[] changes) {
-    DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
-    if (itemBasedString == null) {
-      return;
-    }
-    boolean isClassForName = invoke.getReturnType().descriptor == dexItemFactory.classDescriptor;
-    boolean isReferenceFieldUpdater =
-        invoke.getReturnType().descriptor == dexItemFactory.referenceFieldUpdaterDescriptor;
-    int positionOfIdentifier =
-        isClassForName ? 0 : (isReferenceFieldUpdater ? 2 : 1);
-    Value in = invoke.arguments().get(positionOfIdentifier);
-    Value newIn = insertItemBasedString(code, iterator, invoke, in, itemBasedString);
-    if (newIn != in) {
-      changes[positionOfIdentifier] = newIn;
-    }
-  }
-
-  private Value insertItemBasedString(
-      IRCode code,
-      InstructionListIterator iterator,
-      Instruction base,
-      Value in,
-      DexItemBasedString itemBasedString) {
-    // v_n <- "x.y.z" // in.definition
-    // ...
-    // ... <- ... v_n ..
-    // ...
-    // this.fld <- v_n // base
-    //
-    //   ~>
-    //
-    // ...
-    // v_n' <- DexItemBasedString("Lx/y/z;") // decoupled
-    // this.fld <- v_n' // base
-    //
-    // 1) Move the cursor back to $base
-    iterator.previous();
-    // 2) Add $decoupled just before $base
-    Value newIn = code.createValue(in.outType(), in.getLocalInfo());
-    ConstString decoupled = new ConstString(newIn, itemBasedString);
-    decoupled.setPosition(base.getPosition());
-    iterator.add(decoupled);
-    // 3) Restore the cursor
-    iterator.next();
-    return newIn;
-  }
 }
diff --git a/src/test/examples/identifiernamestring/Main.java b/src/test/examples/identifiernamestring/Main.java
index e29f4d1..6f502ea 100644
--- a/src/test/examples/identifiernamestring/Main.java
+++ b/src/test/examples/identifiernamestring/Main.java
@@ -10,8 +10,12 @@
   public static void main(String[] args) throws Exception {
     A ax = new A();
     assert ax.boo.equals(A.TYPE_B);
-    // Should be renamed
-    ax.bar("identifiernamestring.B");
+    try {
+      // Should be renamed
+      ax.bar("identifiernamestring.B");
+    } catch (NullPointerException e) {
+      System.err.println(e.getMessage());
+    }
 
     Class a = Class.forName(A.TYPE_A);
     Class bByA = Class.forName(B.TYPO_A);