Merge "Ensure that we can merge desugared java8 classes"
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 4ef2370..89542a7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -30,6 +30,7 @@
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
     this.outValue = value;
+    this.outValue.definition = this;
     for (StackValue val : ((StackValues)value).getStackValues()) {
       val.definition = this;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 6d61f58..fcb5b8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -44,6 +44,7 @@
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
     this.outValue = value;
+    this.outValue.definition = this;
     for (StackValue val : ((StackValues)value).getStackValues()) {
       val.definition = this;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index cb93a5e..bef2765 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Deque;
@@ -34,6 +35,7 @@
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class IRCode {
 
@@ -575,7 +577,10 @@
   }
 
   private boolean verifyDefinition(Value value) {
-    assert value.definition.outValue() == value;
+    Value outValue = value.definition.outValue();
+    assert outValue == value
+        || (value instanceof StackValue
+            && Arrays.asList(((StackValues) outValue).getStackValues()).contains(value));
     return true;
   }
 
@@ -662,11 +667,15 @@
           }
           // After the throwing instruction only debug instructions and the final jump
           // instruction is allowed.
+          // TODO(mkroghj) Temporarily allow stack-operations to be after throwing instructions.
           if (seenThrowing) {
             assert instruction.isDebugInstruction()
                 || instruction.isJumpInstruction()
                 || instruction.isStore()
-                || instruction.isPop();
+                || instruction.isPop()
+                || instruction.isDup()
+                || instruction.isDup2()
+                || instruction.isSwap();
           }
         }
       }
@@ -675,7 +684,7 @@
   }
 
   public boolean verifyNoImpreciseOrBottomTypes() {
-    return verifySSATypeLattice(
+    Predicate<Value> verifyValue =
         v -> {
           assert v.getTypeLattice().isPreciseType();
           assert !v.getTypeLattice().isFineGrainedType();
@@ -685,6 +694,16 @@
           assert !(v.definition instanceof ImpreciseMemberTypeInstruction)
               || ((ImpreciseMemberTypeInstruction) v.definition).getMemberType().isPrecise();
           return true;
+        };
+    return verifySSATypeLattice(
+        v -> {
+          // StackValues is an artificial type created to allow returning multiple values from an
+          // instruction.
+          if (v instanceof StackValues) {
+            return Stream.of(((StackValues) v).getStackValues()).allMatch(verifyValue);
+          } else {
+            return verifyValue(v);
+          }
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c010973..e235682 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -64,6 +64,24 @@
   }
 
   @Override
+  public boolean instructionInstanceCanThrow() {
+    return !(size().definition != null
+        && size().definition.isConstNumber()
+        && size().definition.asConstNumber().getRawValue() >= 0
+        && size().definition.asConstNumber().getRawValue() < Integer.MAX_VALUE);
+  }
+
+  @Override
+  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+    if (instructionInstanceCanThrow()) {
+      return false;
+    }
+    // This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo.
+    DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    return baseType.isPrimitiveType() || appInfo.definitionFor(baseType) != null;
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isNewArrayEmpty() && other.asNewArrayEmpty().type == type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index ed51913..f534fb9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -73,6 +74,19 @@
   }
 
   @Override
+  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+    if (!src().getTypeLattice().isNullable() && src().numberOfAllUsers() == 1) {
+      // The NewArrayFilledData instruction is only inserted by an R8 optimization following
+      // a NewArrayEmpty when there are more than one entry.
+      assert src().uniqueUsers().iterator().next() == this;
+      assert src().definition != null;
+      assert src().definition.isNewArrayEmpty();
+      return true;
+    }
+    return false;
+  }
+
+  @Override
   public boolean instructionTypeCanThrow() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 80edc80..3c623d6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -33,6 +33,7 @@
     assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
         value instanceof StackValues;
     this.outValue = value;
+    this.outValue.definition = this;
     for (StackValue val : ((StackValues)value).getStackValues()) {
       val.definition = this;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 4dcc839..6b5f6c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -166,6 +166,7 @@
         reachedFixpoint = !phiOptimizations.optimize(code);
       }
     }
+    assert code.isConsistentSSA();
     registerAllocator = new CfRegisterAllocator(code, options, typeVerificationHelper);
     registerAllocator.allocateRegisters();
     loadStoreHelper.insertPhiMoves(registerAllocator);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index be171c9..fa259c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -115,16 +115,15 @@
         continue;
       }
       Value outValue = current.outValue();
-      // Instructions with no out value cannot be dead code by the current definition
-      // (unused out value). They typically side-effect input values or deals with control-flow.
-      assert outValue != null;
-      if (!outValue.isDead(appInfo)) {
+      if (outValue != null && !outValue.isDead(appInfo)) {
         continue;
       }
       updateWorklist(worklist, current);
       // All users will be removed for this instruction. Eagerly clear them so further inspection
       // of this instruction during dead code elimination will terminate here.
-      outValue.clearUsers();
+      if (outValue != null) {
+        outValue.clearUsers();
+      }
       iterator.removeOrReplaceByDebugLocalRead();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index ad084e4..b02801c 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -473,6 +473,11 @@
           .toCharArray();
       this.packageDictionaryIterator = packageDictionary.iterator();
       this.classDictionaryIterator = classDictionary.iterator();
+
+      // R.class in Android, which contains constant IDs to assets, can be bundled at any time.
+      // Insert `R` immediately so that the class name minifier can skip that name by default.
+      StringBuilder rBuilder = new StringBuilder().append(packagePrefix).append("R;");
+      usedTypeNames.add(appInfo.dexItemFactory.createString(rBuilder.toString()));
     }
 
     public String getPackageName() {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index adc6979..2babc02 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1736,6 +1736,14 @@
           markInstantiated(holderClass.type, KeepReason.reflectiveUseIn(method));
         }
         markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
+        // Fields accessed by reflection is marked as both read and written.
+        if (encodedField.isStatic()) {
+          registerItemWithTargetAndContext(staticFieldsRead, encodedField.field, method);
+          registerItemWithTargetAndContext(staticFieldsWritten, encodedField.field, method);
+        } else {
+          registerItemWithTargetAndContext(instanceFieldsRead, encodedField.field, method);
+          registerItemWithTargetAndContext(instanceFieldsWritten, encodedField.field, method);
+        }
       }
     } else {
       assert identifierItem.isDexMethod();
diff --git a/src/test/examples/classmerging/ArrayTypeCollisionTest.java b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
index 9e4c9fd..c422946 100644
--- a/src/test/examples/classmerging/ArrayTypeCollisionTest.java
+++ b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
@@ -12,11 +12,11 @@
   }
 
   private static void method(A[] obj) {
-    System.out.println("In method(A[])");
+    System.out.println("In method(A[]), length: " + obj.length);
   }
 
   private static void method(B[] obj) {
-    System.out.println("In method(B[])");
+    System.out.println("In method(B[]), length: " + obj.length);
   }
 
   // A cannot be merged into B because that would lead to a collision.
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 3ad2482..0fd0354 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -101,6 +101,9 @@
   // Ordered list of injar entries.
   private List<Path> injars = new ArrayList<>();
 
+  // Ordered list of libraryjar entries.
+  private List<Path> libraryjars = new ArrayList<>();
+
   // Proguard configuration file lines.
   private List<String> config = new ArrayList<>();
 
@@ -138,10 +141,16 @@
         command.add("-injars");
         command.add(injar.toString());
       }
-      command.add("-libraryjars");
-      // TODO(sgjesse): Add support for running with Android Jar.
-      // command.add(ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
-      command.add(ToolHelper.getJava8RuntimeJar().toString());
+      for (Path libraryjar : libraryjars) {
+        command.add("-libraryjars");
+        command.add(libraryjar.toString());
+      }
+      if (libraryjars.isEmpty()) {
+        command.add("-libraryjars");
+        // TODO(sgjesse): Add support for running with Android Jar.
+        // command.add(ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
+        command.add(ToolHelper.getJava8RuntimeJar().toString());
+      }
       command.add("-include");
       command.add(configFile.toString());
       for (Path proguardConfigFile : proguardConfigFiles) {
@@ -198,7 +207,7 @@
         injars.add(file);
       } else {
         throw new Unimplemented(
-            "No support for adding paths directly (we need to compute the descriptor)");
+            "No support for adding class files directly (we need to compute the descriptor)");
       }
     }
     return self();
@@ -207,7 +216,7 @@
   @Override
   public ProguardTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
     throw new Unimplemented(
-        "No support for adding classfile data directly (we need to compute the descriptor)");
+        "No support for adding class files directly (we need to compute the descriptor)");
   }
 
   @Override
@@ -223,7 +232,15 @@
   }
   @Override
   public ProguardTestBuilder addLibraryFiles(Collection<Path> files) {
-    throw new Unimplemented("No support for adding library files");
+    for (Path file : files) {
+      if (FileUtils.isJarFile(file)) {
+        libraryjars.add(file);
+      } else {
+        throw new Unimplemented(
+            "No support for adding class files directly (we need to compute the descriptor)");
+      }
+    }
+    return self();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 42870fa..55730eb 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
 public class R8TestRunResult extends TestRunResult<R8TestRunResult> {
 
@@ -50,6 +51,13 @@
     return graphInspector.get();
   }
 
+  public R8TestRunResult  inspectGraph(Consumer<GraphInspector> consumer)
+      throws IOException, ExecutionException {
+    consumer.accept(graphInspector());
+    return self();
+  }
+
+
   public String proguardMap() {
     return proguardMap;
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index bab857c..e4a809c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -614,10 +614,6 @@
   }
 
   public static Path getAndroidJar(AndroidApiLevel apiLevel) {
-    if (apiLevel == AndroidApiLevel.P) {
-      // TODO(mikaelpeltier) Android P does not yet have his android.jar use the O version
-      apiLevel = AndroidApiLevel.O;
-    }
     String jar = String.format(
         ANDROID_JAR_PATTERN,
         (apiLevel == AndroidApiLevel.getDefault() ? DEFAULT_MIN_SDK : apiLevel).getLevel());
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index c4151d1..688c91c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -206,7 +206,8 @@
             "  public static void main(...);",
             "}",
             "-neverinline class " + main + " {",
-            "  static void method(...);",
+            "  static classmerging.A[] method(...);",
+            "  static classmerging.B[] method(...);",
             "}"));
   }
 
diff --git a/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
new file mode 100644
index 0000000..5de7de0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
@@ -0,0 +1,92 @@
+// 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.deadcode;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Aput;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+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 RemoveDeadArray extends TestBase {
+
+  public static class TestClassWithCatch {
+    static {
+      try {
+        int[] foobar = new int[]{42, 42, 42, 42};
+      } catch (Exception ex) {
+        System.out.println("foobar");
+      }
+    }
+  }
+
+  public static class TestClass {
+    private static int[] foobar = new int[]{42, 42, 42, 42};
+    public static void main(java.lang.String[] args) {
+      ShouldGoAway[] willBeRemoved = new ShouldGoAway[4];
+      ShouldGoAway[] willAlsoBeRemoved = new ShouldGoAway[0];
+      System.out.println("foobar");
+    }
+
+    public static class ShouldGoAway { }
+  }
+
+  private Backend backend;
+
+  public RemoveDeadArray(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    // Todo(ricow): enable unused array removal for cf backend.
+    return new Backend[]{Backend.DEX};
+  }
+
+
+  @Test
+  public void testDeadArraysRemoved() throws Exception {
+    R8TestCompileResult result =
+        testForR8(backend)
+            .addProgramClasses(TestClass.class, TestClass.ShouldGoAway.class)
+            .addKeepMainRule(TestClass.class)
+            .compile();
+    CodeInspector inspector = result.inspector();
+    assertFalse(inspector.clazz(TestClass.class).clinit().isPresent());
+
+    MethodSubject main = inspector.clazz(TestClass.class).mainMethod();
+    main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray());
+    assertFalse(main.getMethod().getCode().asDexCode().toString().contains("NewArray"));
+    runOnArt(result.app, TestClass.class.getName());
+ }
+
+  @Test
+  public void testNotRemoveStaticCatch() throws Exception {
+    R8TestCompileResult result =
+        testForR8(backend)
+            .addProgramClasses(TestClassWithCatch.class)
+            .addKeepAllClassesRule()
+            .compile();
+    CodeInspector inspector = result.inspector();
+    MethodSubject clinit = inspector.clazz(TestClassWithCatch.class).clinit();
+    assertTrue(clinit.isPresent());
+    // Ensure that our optimization does not hit, we should still have 4 Aput instructions.
+    long aPutCount = Arrays.stream(clinit.getMethod().getCode().asDexCode().instructions)
+        .filter(instruction -> instruction instanceof  Aput)
+        .count();
+    assertEquals(4, aPutCount);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/AvoidRTest.java b/src/test/java/com/android/tools/r8/naming/AvoidRTest.java
new file mode 100644
index 0000000..4633b36
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AvoidRTest.java
@@ -0,0 +1,144 @@
+// 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;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+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 AvoidRTest extends JasminTestBase {
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public AvoidRTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test_withObfuscationDictionary() throws Exception {
+    Path dictionary = temp.newFile("dictionary.txt").toPath();
+    FileUtils.writeTextFile(dictionary, StringUtils.lines("P", "Q", "R", "S", "T"));
+    Set<String> expectedNames = ImmutableSet.of("P", "Q", "S", "T");
+
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    R8TestBuilder builder = testForR8(backend);
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("TopLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("p1/SecondLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("p1/p2/ThirdLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("p2/SecondLevel" + Integer.toString(i));
+    }
+    builder.addProgramClassFileData(jasminBuilder.buildClasses());
+    Set<String> usedDescriptors = new HashSet<>();
+    builder.noTreeShaking()
+        .addKeepRules("-classobfuscationdictionary " + dictionary)
+        .compile()
+        .inspect(codeInspector -> {
+          codeInspector.forAllClasses(classSubject -> {
+            assertThat(classSubject, isRenamed());
+            String renamedDescriptor = classSubject.getFinalDescriptor();
+            assertTrue(usedDescriptors.add(renamedDescriptor));
+            assertNotEquals("R", getSimpleClassNameFromDescriptor(renamedDescriptor));
+            assertTrue(expectedNames.contains(getSimpleClassNameFromDescriptor(renamedDescriptor)));
+          });
+        });
+  }
+
+  @Test
+  public void test_withoutPackageHierarchy() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    R8TestBuilder builder = testForR8(backend);
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("TestClass" + Integer.toString(i));
+    }
+    builder.addProgramClassFileData(jasminBuilder.buildClasses());
+    Set<String> usedNames = new HashSet<>();
+    builder.noTreeShaking()
+        .compile()
+        .inspect(codeInspector -> {
+          codeInspector.forAllClasses(classSubject -> {
+            assertThat(classSubject, isRenamed());
+            assertTrue(usedNames.add(classSubject.getFinalName()));
+            assertNotEquals("R", classSubject.getFinalName());
+          });
+        });
+    assertTrue(usedNames.contains("Q"));
+    assertTrue(usedNames.contains("S"));
+  }
+
+  private void test_withPackageHierarchy(String keepRule) throws Exception {
+    R8TestBuilder builder = testForR8(backend);
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("TopLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("p1/SecondLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("p1/p2/ThirdLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("p2/SecondLevel" + Integer.toString(i));
+    }
+    builder.addProgramClassFileData(jasminBuilder.buildClasses());
+    Set<String> usedDescriptors = new HashSet<>();
+    builder.noTreeShaking()
+        .addKeepRules(keepRule)
+        .compile()
+        .inspect(codeInspector -> {
+          codeInspector.forAllClasses(classSubject -> {
+            assertThat(classSubject, isRenamed());
+            String renamedDescriptor = classSubject.getFinalDescriptor();
+            assertTrue(usedDescriptors.add(renamedDescriptor));
+            assertNotEquals("R", getSimpleClassNameFromDescriptor(renamedDescriptor));
+          });
+        });
+  }
+
+  @Test
+  public void test_withPackageHierarchy_default() throws Exception {
+    test_withPackageHierarchy("");
+  }
+
+  @Test
+  public void test_withPackageHierarchy_repackage() throws Exception {
+    // Repackage every class to the top-level.
+    test_withPackageHierarchy("-repackageclasses");
+  }
+
+  @Test
+  public void test_withPackageHierarchy_flatten() throws Exception {
+    // Repackage every package to the top-level.
+    test_withPackageHierarchy("-flattenpackagehierarchy");
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
index e8eb416..e518181 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -8,7 +8,9 @@
 @Keep
 public class KeptByFieldReflectionTest {
 
-  public final int foo = 42;
+  // TODO(b/123262024): This field must be kept un-initialized. Otherwise the "-whyareyoukeeping"
+  // output tested will hit the initialization in <init> and not the reflective access.
+  public int foo;
 
   public static void main(String[] args) throws Exception {
     // Due to b/123210548 the object cannot be created by a reflective newInstance call.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index 6feaa35..62dcebe 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -19,7 +19,6 @@
 import java.io.PrintStream;
 import java.util.Arrays;
 import java.util.Collection;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,7 +30,7 @@
   private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
   private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
 
-  private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
+  private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 0");
 
   private final String EXPECTED_WHYAREYOUKEEPING =
       StringUtils.lines(
@@ -53,7 +52,6 @@
   }
 
   @Test
-  @Ignore("b/123215165")
   public void test() throws Exception {
     MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
     FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
diff --git a/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
new file mode 100644
index 0000000..7aa573e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
@@ -0,0 +1,161 @@
+// 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.shaking.reflection;
+import static com.android.tools.r8.references.Reference.fieldFromField;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import org.junit.Assert;
+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 FieldAccessTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public FieldAccessTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  private boolean isInvokeGetField(InstructionSubject instruction) {
+    return
+        instruction.isInvoke()
+            && instruction.getMethod().qualifiedName().equals("java.lang.Class.getField");
+  }
+
+  private void runTest(Class<?> testClass) throws Exception {
+    MethodReference mainMethod =
+        methodFromMethod(testClass.getDeclaredMethod("main", String[].class));
+    FieldReference fooField = fieldFromField(testClass.getDeclaredField("foo"));
+
+    testForR8(Backend.DEX)
+        .enableGraphInspector()
+        .addProgramClasses(testClass)
+        .addKeepMainRule(testClass)
+        .run(testClass)
+        .inspectGraph(inspector -> {
+          // The only root should be the keep annotation rule.
+          assertEquals(1, inspector.getRoots().size());
+          QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+          inspector.method(mainMethod).assertNotRenamed().assertKeptBy(root);
+          inspector.field(fooField).assertRenamed();
+        })
+        .inspect(inspector -> {
+          Assert.assertTrue(
+              inspector
+                  .clazz(testClass)
+                  .uniqueMethodWithName("main")
+                  .streamInstructions()
+                  .anyMatch(this::isInvokeGetField));
+        })
+        .assertSuccessWithOutput("42");
+  }
+
+  @Test
+  public void reflectiveGet() throws Exception {
+    runTest(FieldAccessTestGet.class);
+  }
+
+  @Test
+  public void reflectiveGetStatic() throws Exception {
+    runTest(FieldAccessTestGetStatic.class);
+  }
+
+  @Test
+  public void reflectivePut() throws Exception {
+    runTest(FieldAccessTestPut.class);
+  }
+
+  @Test
+  public void reflectivePutStatic() throws Exception {
+    runTest(FieldAccessTestPutStatic.class);
+  }
+
+  @Test
+  public void reflectivePutGet() throws Exception {
+    runTest(FieldAccessTestPutGet.class);
+  }
+
+  @Test
+  public void reflectivePutGetStatic() throws Exception {
+    runTest(FieldAccessTestPutGetStatic.class);
+  }
+}
+
+class FieldAccessTestGet {
+
+  public int foo = 42;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestGet obj = new FieldAccessTestGet();
+    System.out.print(FieldAccessTestGet.class.getField("foo").getInt(obj));
+  }
+}
+
+class FieldAccessTestGetStatic {
+
+  public static int foo = 42;
+
+  public static void main(String[] args) throws Exception {
+    System.out.print(FieldAccessTestGetStatic.class.getField("foo").getInt(null));
+  }
+}
+
+class FieldAccessTestPut {
+
+  public int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPut obj = new FieldAccessTestPut();
+    FieldAccessTestPut.class.getField("foo").setInt(obj, 42);
+    System.out.print(42);
+  }
+}
+
+class FieldAccessTestPutStatic {
+
+  public static int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPutStatic.class.getField("foo").setInt(null, 42);
+    System.out.print(42);
+  }
+}
+
+class FieldAccessTestPutGet {
+
+  public int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPutGet obj = new FieldAccessTestPutGet();
+    FieldAccessTestPutGet.class.getField("foo").setInt(obj, 42);
+    System.out.print(FieldAccessTestPutGet.class.getField("foo").getInt(obj));
+  }
+}
+
+class FieldAccessTestPutGetStatic {
+
+  public static int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPutGetStatic.class.getField("foo").setInt(null, 42);
+    System.out.print(FieldAccessTestPutGetStatic.class.getField("foo").getInt(null));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index ee2155e..375f0b5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfMonitor;
 import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturn;
@@ -280,6 +281,11 @@
   }
 
   @Override
+  public boolean isNewArray() {
+    return instruction instanceof CfNewArray;
+  }
+
+  @Override
   public int size() {
     // TODO(b/122302789): CfInstruction#getSize()
     throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 7051226..e2e801e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -67,6 +67,7 @@
 import com.android.tools.r8.code.MulIntLit8;
 import com.android.tools.r8.code.MulLong;
 import com.android.tools.r8.code.MulLong2Addr;
+import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.code.PackedSwitch;
@@ -360,6 +361,11 @@
   }
 
   @Override
+  public boolean isNewArray() {
+    return instruction instanceof NewArray;
+  }
+
+  @Override
   public boolean isMonitorEnter() {
     return instruction instanceof MonitorEnter;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 294cc8c..dd5af7c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -82,6 +82,8 @@
 
   boolean isMultiplication();
 
+  boolean isNewArray();
+
   boolean isMonitorEnter();
 
   boolean isMonitorExit();
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 93c910e..b4eb396 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -66,6 +66,44 @@
       if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
         f.write(line)
 
+def GetMinAndCompileSdk(app, config, checkout_dir, apk_reference):
+  app_module = config.get('app_module', 'app')
+  build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle')
+  assert os.path.isfile(build_gradle_file), (
+      'Expected to find build.gradle file at {}'.format(build_gradle_file))
+
+  compile_sdk = None
+  min_sdks = []
+  target_sdk = None
+
+  with open(build_gradle_file) as f:
+    for line in f.readlines():
+      stripped = line.strip()
+      if stripped.startswith('compileSdkVersion '):
+        assert not compile_sdk
+        compile_sdk = int(stripped[len('compileSdkVersion '):])
+      if stripped.startswith('minSdkVersion '):
+        min_sdks.append(int(stripped[len('minSdkVersion '):]))
+      elif stripped.startswith('targetSdkVersion '):
+        assert not target_sdk
+        target_sdk = int(stripped[len('targetSdkVersion '):])
+
+  if len(min_sdks) == 1:
+    min_sdk = min_sdks[0]
+  else:
+    assert 'min_sdk' in config
+    min_sdk = config.get('min_sdk')
+
+  assert min_sdk, (
+      'Expected to find `minSdkVersion` in {}'.format(build_gradle_file))
+  assert compile_sdk, (
+      'Expected to find `compileSdkVersion` in {}'.format(build_gradle_file))
+
+  assert not target_sdk or target_sdk == compile_sdk, (
+      'Expected `compileSdkVersion` and `targetSdkVersion` to be the same')
+
+  return (min_sdk, compile_sdk)
+
 def IsGradleTaskName(x):
   # Check that it is non-empty.
   if not x:
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index a74f868..fcd0a76 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -78,6 +78,7 @@
       'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
       'flavor': 'standard',
       'releaseTarget': 'app:assembleRelease',
+      'min_sdk': 16
   },
   'tivi': {
       'app_id': 'app.tivi',
@@ -292,6 +293,10 @@
             with open(proguard_config_file) as old:
               assert(sum(1 for line in new) > sum(1 for line in old))
 
+          # Extract min-sdk and target-sdk
+          (min_sdk, compile_sdk) = as_utils.GetMinAndCompileSdk(app, config,
+              checkout_dir, apk_dest)
+
           # Now rebuild generated apk.
           previous_apk = apk_dest
           for i in range(1, options.r8_compilation_steps):
@@ -300,7 +305,7 @@
                   checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
               RebuildAppWithShrinker(
                   previous_apk, recompiled_apk_dest, ext_proguard_config_file,
-                  shrinker)
+                  shrinker, min_sdk, compile_sdk)
               recompilation_result = {
                 'apk_dest': recompiled_apk_dest,
                 'build_status': 'success',
@@ -422,19 +427,28 @@
 
   return (apk_dest, profile_dest_dir, proguard_config_dest)
 
-def RebuildAppWithShrinker(apk, apk_dest, proguard_config_file, shrinker):
+def RebuildAppWithShrinker(
+    apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk):
   assert 'r8' in shrinker
   assert apk_dest.endswith('.apk')
 
   # Compile given APK with shrinker to temporary zip file.
-  api = 28 # TODO(christofferqa): Should be the one from build.gradle
-  android_jar = os.path.join(utils.REPO_ROOT, utils.ANDROID_JAR.format(api=api))
+  android_jar = utils.get_android_jar(compile_sdk)
   r8_jar = utils.R8LIB_JAR if IsMinifiedR8(shrinker) else utils.R8_JAR
   zip_dest = apk_dest[:-4] + '.zip'
 
-  cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar,
-      'com.android.tools.r8.R8', '--release', '--pg-conf', proguard_config_file,
+  # TODO(christofferqa): Entry point should be CompatProguard if the shrinker
+  # is 'r8'.
+  entry_point = 'com.android.tools.r8.R8'
+
+  cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar, entry_point,
+      '--release', '--min-api', str(min_sdk), '--pg-conf', proguard_config_file,
       '--lib', android_jar, '--output', zip_dest, apk]
+
+  for android_optional_jar in utils.get_android_optional_jars(compile_sdk):
+    cmd.append('--lib')
+    cmd.append(android_optional_jar)
+
   utils.PrintCmd(cmd)
 
   subprocess.check_output(cmd)
diff --git a/tools/utils.py b/tools/utils.py
index f8b4469..72febdc 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -13,7 +13,8 @@
 import tarfile
 import tempfile
 
-ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
+ANDROID_JAR_DIR = 'third_party/android_jar/lib-v{api}'
+ANDROID_JAR = os.path.join(ANDROID_JAR_DIR, 'android.jar')
 TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
 REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..'))
 THIRD_PARTY = os.path.join(REPO_ROOT, 'third_party')
@@ -308,8 +309,23 @@
   if m is not None:
     raise Exception("Do not use google JVM for benchmarking: " + version)
 
+def get_android_jar_dir(api):
+  return os.path.join(REPO_ROOT, ANDROID_JAR_DIR.format(api=api))
+
 def get_android_jar(api):
   return os.path.join(REPO_ROOT, ANDROID_JAR.format(api=api))
 
+def get_android_optional_jars(api):
+  android_optional_jars_dir = os.path.join(get_android_jar_dir(api), 'optional')
+  android_optional_jars = [
+    os.path.join(android_optional_jars_dir, 'android.test.base.jar'),
+    os.path.join(android_optional_jars_dir, 'android.test.mock.jar'),
+    os.path.join(android_optional_jars_dir, 'android.test.runner.jar'),
+    os.path.join(android_optional_jars_dir, 'org.apache.http.legacy.jar')
+  ]
+  return [
+      android_optional_jar for android_optional_jar in android_optional_jars
+      if os.path.isfile(android_optional_jar)]
+
 def is_bot():
   return 'BUILDBOT_BUILDERNAME' in os.environ