Handle StringSwitch with class name value

Bug: 135560746
Change-Id: I26f6512701a5ba55fe276f9286624afb190d5cfd
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9964c7f..dd8228c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -201,14 +201,11 @@
             : null;
     this.stringOptimizer = new StringOptimizer(appView);
     this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
-    this.stringSwitchRemover =
-        options.isStringSwitchConversionEnabled() ? new StringSwitchRemover(appView) : null;
     this.nonNullTracker = options.enableNonNullTracking ? new NonNullTracker(appView) : null;
     if (appView.enableWholeProgramOptimizations()) {
       assert appView.appInfo().hasLiveness();
       assert appView.rootSet() != null;
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
       this.classInliner =
           options.enableClassInlining && options.enableInlining
               ? new ClassInliner(lambdaRewriter)
@@ -259,6 +256,10 @@
     }
     this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
     this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
+    this.stringSwitchRemover =
+        options.isStringSwitchConversionEnabled()
+            ? new StringSwitchRemover(appView, identifierNameStringMarker)
+            : null;
   }
 
   public Set<DexCallSite> getDesugaredCallSites() {
@@ -1054,8 +1055,7 @@
     codeRewriter.splitRangeInvokeConstants(code);
     new SparseConditionalConstantPropagation(code).run();
     if (stringSwitchRemover != null) {
-      // TODO(b/135588279): Add support for string-switch instruction in the CF and DEX backend.
-      stringSwitchRemover.run(code);
+      stringSwitchRemover.run(method, code);
     }
     codeRewriter.rewriteSwitch(code);
     codeRewriter.processMethodsNeverReturningNormally(code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index 6507f22..b6a2ac3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -21,41 +22,51 @@
 import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StringSwitch;
+import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.IdentityHashMap;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 
-// TODO(b/135588279): Add support for string-switch instruction in the CF and DEX backend.
 public class StringSwitchRemover {
 
   private final AppView<?> appView;
+  private final IdentifierNameStringMarker identifierNameStringMarker;
   private final ClassTypeLatticeElement stringType;
   private final ThrowingInfo throwingInfo;
 
-  StringSwitchRemover(AppView<?> appView) {
+  StringSwitchRemover(AppView<?> appView, IdentifierNameStringMarker identifierNameStringMarker) {
     this.appView = appView;
+    this.identifierNameStringMarker = identifierNameStringMarker;
     this.stringType = TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
     this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
   }
 
-  void run(IRCode code) {
+  void run(DexEncodedMethod method, IRCode code) {
     if (!code.hasStringSwitch) {
       assert Streams.stream(code.instructions()).noneMatch(Instruction::isStringSwitch);
       return;
     }
 
+    Set<BasicBlock> newBlocks = Sets.newIdentityHashSet();
+
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       JumpInstruction exit = block.exit();
       if (exit.isStringSwitch()) {
-        removeStringSwitch(code, blockIterator, block, exit.asStringSwitch());
+        removeStringSwitch(code, blockIterator, block, exit.asStringSwitch(), newBlocks);
       }
     }
 
+    if (identifierNameStringMarker != null) {
+      identifierNameStringMarker.decoupleIdentifierNameStringsInBlocks(method, code, newBlocks);
+    }
+
     assert code.isConsistentSSA();
   }
 
@@ -63,7 +74,8 @@
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       BasicBlock block,
-      StringSwitch theSwitch) {
+      StringSwitch theSwitch,
+      Set<BasicBlock> newBlocks) {
     BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
     Map<DexString, BasicBlock> stringToTargetMap = new IdentityHashMap<>();
     theSwitch.forEachCase(stringToTargetMap::put);
@@ -96,6 +108,7 @@
               code.blocks.size(), ifInstruction, constStringInstruction, invokeInstruction);
       newBlock.link(entry.getValue());
       blockIterator.add(newBlock);
+      newBlocks.add(newBlock);
 
       if (previous == null) {
         // Replace the string-switch instruction by a goto instruction.
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 87f4c10..34ce2c4 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -45,6 +45,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 public class IdentifierNameStringMarker {
@@ -83,13 +84,21 @@
     }
   }
 
-  public void decoupleIdentifierNameStringsInMethod(DexEncodedMethod encodedMethod, IRCode code) {
+  public void decoupleIdentifierNameStringsInMethod(DexEncodedMethod method, IRCode code) {
+    decoupleIdentifierNameStringsInBlocks(method, code, null);
+  }
+
+  public void decoupleIdentifierNameStringsInBlocks(
+      DexEncodedMethod method, IRCode code, Set<BasicBlock> blocks) {
     if (!code.hasConstString) {
       return;
     }
-    ListIterator<BasicBlock> blocks = code.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
+    ListIterator<BasicBlock> blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      if (blocks != null && !blocks.contains(block)) {
+        continue;
+      }
       InstructionListIterator iterator = block.listIterator();
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
@@ -107,11 +116,11 @@
         if (instruction.isStaticPut() || instruction.isInstancePut()) {
           iterator =
               decoupleIdentifierNameStringForFieldPutInstruction(
-                  code, encodedMethod, blocks, iterator, instruction.asFieldInstruction());
+                  code, method, blockIterator, iterator, instruction.asFieldInstruction());
         } else if (instruction.isInvokeMethod()) {
           iterator =
               decoupleIdentifierNameStringForInvokeInstruction(
-                  code, encodedMethod, blocks, iterator, instruction.asInvokeMethod());
+                  code, method, blockIterator, iterator, instruction.asInvokeMethod());
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 565c3e0..1f27829 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -33,8 +33,6 @@
         .addProgramFiles(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR)
         .addKeepMainRule("proto2.TestClass")
         .addKeepRules(
-            // TODO(b/112437944): Fix -identifiernamestring support.
-            "-keepnames class * extends com.google.protobuf.GeneratedMessageLite",
             // TODO(b/112437944): Use dex item based const strings for proto schema definitions.
             "-keepclassmembernames class * extends com.google.protobuf.GeneratedMessageLite {",
             "  <fields>;",
diff --git a/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonSwitchTest.java b/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonSwitchTest.java
new file mode 100644
index 0000000..38f1534
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonSwitchTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassNameComparisonSwitchTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public ClassNameComparisonSwitchTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassNameComparisonSwitchTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              assert !options.enableStringSwitchConversion;
+              options.enableStringSwitchConversion = true;
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::verifyClassesHaveBeenMinified)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A", "B", "C");
+  }
+
+  private void verifyClassesHaveBeenMinified(CodeInspector inspector) {
+    for (Class<?> clazz : ImmutableList.of(A.class, B.class, C.class)) {
+      ClassSubject classSubject = inspector.clazz(clazz);
+      assertThat(classSubject, isPresent());
+      assertThat(classSubject, isRenamed());
+    }
+  }
+
+  @Test
+  public void testCorrectnessOfNames() {
+    assertEquals(A.class.getName(), TestClass.NAME_A);
+    assertEquals(B.class.getName(), TestClass.NAME_B);
+  }
+
+  static class TestClass {
+
+    private static final String NAME_A =
+        "com.android.tools.r8.naming.identifiernamestring.ClassNameComparisonSwitchTest$A";
+
+    private static final String NAME_B =
+        "com.android.tools.r8.naming.identifiernamestring.ClassNameComparisonSwitchTest$B";
+
+    public static void main(String[] args) {
+      System.out.println(test(A.class));
+      System.out.println(test(B.class));
+      System.out.println(test(C.class));
+    }
+
+    @NeverInline
+    static String test(Class<?> clazz) {
+      switch (clazz.getName()) {
+        case NAME_A:
+          return "A";
+        case NAME_B:
+          return "B";
+        default:
+          return "C";
+      }
+    }
+  }
+
+  static class A {}
+
+  static class B {}
+
+  static class C {}
+}