Fix soundness in enum switch map removal

Bug: 172528424
Change-Id: Ieda83802b24b1d3de64399fef89c63938f2b072d
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index f1c2bdd..033a233 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -228,16 +228,9 @@
         continue;
       }
 
-      Int2IntMap ordinalToTargetMap = new Int2IntArrayMap(switchInsn.numberOfKeys());
-      for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
-        assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
-        DexField field = info.indexMap.get(switchInsn.getKey(i));
-        EnumValueInfo valueInfo = info.valueInfoMap.getEnumValueInfo(field);
-        if (valueInfo != null) {
-          ordinalToTargetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
-        } else {
-          // The switch map refers to a field on the enum that does not exist in this compilation.
-        }
+      Int2IntMap ordinalToTargetMap = computeOrdinalToTargetMap(switchInsn, info);
+      if (ordinalToTargetMap == null) {
+        continue;
       }
 
       int fallthroughBlockIndex = switchInsn.getFallthroughBlockIndex();
@@ -322,6 +315,24 @@
     }
   }
 
+  private Int2IntMap computeOrdinalToTargetMap(IntSwitch switchInsn, EnumSwitchInfo info) {
+    Int2IntMap ordinalToTargetMap = new Int2IntArrayMap(switchInsn.numberOfKeys());
+    for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
+      assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
+      DexField field = info.indexMap.get(switchInsn.getKey(i));
+      EnumValueInfo valueInfo = info.valueInfoMap.getEnumValueInfo(field);
+      if (valueInfo != null) {
+        if (appView.appInfo().isPinned(field)) {
+          return null;
+        }
+        ordinalToTargetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
+      } else {
+        // The switch map refers to a field on the enum that does not exist in this compilation.
+      }
+    }
+    return ordinalToTargetMap;
+  }
+
   private static final class EnumSwitchInfo {
 
     final DexType enumClass;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidOrdinalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidOrdinalTest.java
new file mode 100644
index 0000000..cbbd4f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidOrdinalTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2020, 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.ir.optimize.switches;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SwitchMapInvalidOrdinalTest extends TestBase {
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public SwitchMapInvalidOrdinalTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(SwitchMapInvalidOrdinalTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "b", "0", "a");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-keep class"
+                + "com.android.tools.r8.ir.optimize.switches.SwitchMapInvalidOrdinalTest$MyEnum {"
+                + " static <fields>; }")
+        .addInnerClasses(SwitchMapInvalidOrdinalTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        // When the code reaches the switch the first time, then the switch map int[] gets
+        // initialized based on the values in the enum at this point creating a mapping ordinal to
+        // switch map entry. Here D and X have 3 as ordinal.
+        .assertSuccessWithOutputLines("a", "b", "0", "a");
+  }
+
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B,
+    C,
+    D;
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      try {
+        // Use reflection to instantiate a new enum instance, and set it to A, using getFields
+        // and not getField since A is minified.
+        Constructor<MyEnum> constructor =
+            (Constructor<MyEnum>) MyEnum.class.getDeclaredConstructors()[0];
+        constructor.setAccessible(true);
+        MyEnum x = constructor.newInstance("X", 3);
+        Field f = MyEnum.class.getFields()[0];
+
+        // On Android, setAccessible allows to set a final field.
+        f.setAccessible(true);
+
+        f.set(null, x);
+      } catch (Exception e) {
+        System.out.println("Unexpected: " + e);
+      }
+      print(MyEnum.A);
+      print(MyEnum.B);
+      print(MyEnum.C);
+      print(MyEnum.D);
+    }
+
+    private static void print(MyEnum e) {
+      switch (e) {
+        case A:
+          System.out.println("a");
+          break;
+        case B:
+          System.out.println("b");
+          break;
+        default:
+          System.out.println("0");
+      }
+    }
+  }
+}