Version 1.4.29

Cherry-pick: Fix not setting offset on DexItemBasedConstString replacement
CL: https://r8-review.googlesource.com/c/r8/+/33145

Cherry-pick: Add test for gms core with forced jumbo string rewriting
CL: https://r8-review.googlesource.com/c/r8/+/33228

Cherry-pick: Don't generate compat rules when tracing reflective use
CL: https://r8-review.googlesource.com/c/r8/+/32845

Cherry-pick: Mark fields accessed by reflection as both read and written
CL: https://r8-review.googlesource.com/c/r8/+/33271

Cherry-pick: Print proper reason for kept-by-reflection edges.
CL: https://r8-review.googlesource.com/c/r8/+/33263

Cherry-pick: Ensure that we can merge desugared java8 classes
CL: https://r8-review.googlesource.com/c/r8/+/33281

Cherry-pick: Add support for -libraryjars in ProguardTestBuilder.
CL: https://r8-review.googlesource.com/c/r8/+/33274

Cherry-pick: Throw for unimplemented APIs in ProguardTestBuilder
CL: https://r8-review.googlesource.com/c/r8/+/33262

Cherry-pick: Guard check-cast elimination when in-value is null constant
CL: https://r8-review.googlesource.com/c/r8/+/33300

Cherry-pick: Revert "Revert "Assert that definitionFor is only called on non-null class types.""
CL: https://r8-review.googlesource.com/c/r8/+/33280

Bug: 123152027
Bug: 123242448
Bug: 123215165
Bug: 69590587
Bug: 123164442
Bug: 123269162
Change-Id: I06db0b137ceb3d3410f04680c5bbecc697b8f905
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 5b36379..a7b396e 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.28";
+  public static final String LABEL = "1.4.29";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index 0c838ac..e3fa01b 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -16,9 +16,11 @@
     InvokedFrom,
     InvokedFromLambdaCreatedIn,
     ReferencedFrom,
+    ReflectiveUseFrom,
     ReachableFromLiveType,
     ReferencedInAnnotation,
     IsLibraryMethod,
+    MethodHandleUseFrom
   }
 
   private final EdgeKind kind;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a45064e..e415b0f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -119,8 +119,10 @@
 
   // For mapping invoke virtual instruction to target methods.
   public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
-    Set<DexEncodedMethod> result = new HashSet<>();
-    // First add the target for receiver type method.type.
+    if (method.holder.isArrayType()) {
+      assert method.name == dexItemFactory.cloneMethodName;
+      return null;
+    }
     DexClass root = definitionFor(method.holder);
     if (root == null) {
       // type specified in method does not have a materialized class.
@@ -131,6 +133,8 @@
       // This will fail at runtime.
       return null;
     }
+    // First add the target for receiver type method.type.
+    Set<DexEncodedMethod> result = new HashSet<>();
     topTargets.forEachTarget(result::add);
     // Add all matching targets from the subclass hierarchy.
     for (DexType type : subtypes(method.holder)) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 09094aa..1e125d9 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -45,6 +45,7 @@
 
   @Override
   public DexClass definitionFor(DexType type) {
+    assert type.isClassType() : "Cannot lookup definition for type: " + type;
     DexClass result = programClasses.get(type);
     if (result == null) {
       result = libraryClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 6dea825..b236428 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -42,9 +42,7 @@
 
   @Override
   public DexClass definitionFor(DexType type) {
-    if (type == null) {
-      return null;
-    }
+    assert type.isClassType() : "Cannot lookup definition for type: " + type;
     DexClass clazz = programClasses.get(type);
     if (clazz == null && classpathClasses != null) {
       clazz = classpathClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index be7709c..456d45f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -78,7 +78,7 @@
       return true;
     }
 
-    if (valueType.isReference()) {
+    if (fieldType.isClassType() && valueType.isReference()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
       DexClass clazz = appInfo.definitionFor(instruction.getField().type);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 0b8ef55..c52bcf9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -90,6 +90,9 @@
     // A const-class instruction can be dead code only if the resulting program is known to contain
     // the class mentioned.
     DexType baseType = clazz.toBaseType(appInfo.dexItemFactory);
+    if (baseType.isPrimitiveType()) {
+      return true;
+    }
     DexClass holder = appInfo.definitionFor(baseType);
     return holder != null && holder.isProgramClass();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
index c0a7569..9e2e9ab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -84,6 +84,10 @@
     return null;
   }
 
+  public static boolean hasJava8MethodRewritePrefix(DexType clazz) {
+    return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
+  }
+
   public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
     if (holders.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 6a7e6ba..e3257d5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2096,6 +2096,16 @@
       return false;
     }
 
+    // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+    // elimination may lead to verification errors. See b/123269162.
+    if (options.canHaveArtCheckCastVerifierBug()) {
+      if (inValue.getTypeLattice().isNullType()
+          && castType.isArrayType()
+          && castType.toBaseType(dexItemFactory).isFloatType()) {
+        return false;
+      }
+    }
+
     // We might see chains of casts on subtypes. It suffices to cast to the lowest subtype,
     // as that will fail if a cast on a supertype would have failed.
     Predicate<Instruction> isCheckcastToSubtype =
@@ -2140,6 +2150,9 @@
 
   private boolean isTypeInaccessibleInCurrentContext(DexType type, DexEncodedMethod context) {
     DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    if (baseType.isPrimitiveType()) {
+      return false;
+    }
     DexClass clazz = definitionFor(baseType);
     if (clazz == null) {
       // Conservatively say yes.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 7375303..cdf37c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -113,6 +113,9 @@
 
     eligibleClass =
         root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+    if (!eligibleClass.isClassType()) {
+      return false;
+    }
     eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
     if (eligibleClassDefinition == null && lambdaRewriter != null) {
       // Check if the class is synthesized for a desugared lambda
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 61695f1..e670925 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -170,7 +170,9 @@
                           appInfo.definitionFor(cnst.getItem().asDexType()),
                           cnst.getClassNameComputationInfo()))
                   : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
-          instructions[i] = new ConstString(cnst.AA, replacement);
+          ConstString constString = new ConstString(cnst.AA, replacement);
+          constString.setOffset(instruction.getOffset());
+          instructions[i] = constString;
         }
       }
     } else {
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 3376854..86a4a8d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -604,7 +604,7 @@
       if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
         DexClass holder = appInfo.definitionFor(methodHandle.asMethod().holder);
         if (holder != null) {
-          markClassAsInstantiatedWithMethodHandleRule(holder);
+          markInstantiated(holder.type, KeepReason.methodHandleReferencedIn(currentMethod));
         }
       }
     }
@@ -1189,8 +1189,10 @@
 
   private void markVirtualMethodAsLive(DexEncodedMethod method, KeepReason reason) {
     assert method != null;
-    // Only explicit keep rules should make abstract methods live.
-    assert !method.accessFlags.isAbstract() || reason.isDueToKeepRule();
+    // Only explicit keep rules or reflective use should make abstract methods live.
+    assert !method.accessFlags.isAbstract()
+        || reason.isDueToKeepRule()
+        || reason.isDueToReflectiveUse();
     if (!liveMethods.contains(method)) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Adding virtual method `%s` to live set.", method.method);
@@ -1662,16 +1664,8 @@
             collectReachedFields(staticFields, this::tryLookupStaticField)));
   }
 
-  private void markClassAsInstantiatedWithMethodHandleRule(DexClass clazz) {
-    ProguardKeepRule rule =
-        ProguardConfigurationUtils.buildMethodHandleKeepRule(clazz);
-    proguardCompatibilityWorkList.add(
-        Action.markInstantiated(clazz, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
-  }
-
   private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
-    ProguardKeepRule rule =
-        ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+    ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
     proguardCompatibilityWorkList.add(
         Action.markInstantiated(clazz, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
     if (clazz.hasDefaultInitializer()) {
@@ -1681,19 +1675,6 @@
     }
   }
 
-  private void markFieldAsKeptWithCompatRule(DexEncodedField field, boolean keepClass) {
-    DexClass holderClass = appInfo.definitionFor(field.field.getHolder());
-    ProguardKeepRule rule =
-        ProguardConfigurationUtils.buildFieldKeepRule(holderClass, field, keepClass);
-    if (keepClass) {
-      proguardCompatibilityWorkList.add(
-          Action.markInstantiated(
-              holderClass, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
-    }
-    proguardCompatibilityWorkList.add(
-        Action.markFieldKept(field, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
-  }
-
   private void markMethodAsKeptWithCompatRule(DexEncodedMethod method) {
     DexClass holderClass = appInfo.definitionFor(method.method.getHolder());
     ProguardKeepRule rule =
@@ -1733,7 +1714,11 @@
     if (identifierItem.isDexType()) {
       DexClass clazz = appInfo.definitionFor(identifierItem.asDexType());
       if (clazz != null) {
-        markClassAsInstantiatedWithCompatRule(clazz);
+        markInstantiated(clazz.type, KeepReason.reflectiveUseIn(method));
+        if (clazz.hasDefaultInitializer()) {
+          markDirectStaticOrConstructorMethodAsLive(
+              clazz.getDefaultInitializer(), KeepReason.reflectiveUseIn(method));
+        }
       }
     } else if (identifierItem.isDexField()) {
       DexEncodedField encodedField = appInfo.definitionFor(identifierItem.asDexField());
@@ -1746,13 +1731,30 @@
         boolean keepClass =
             !encodedField.accessFlags.isStatic()
                 && appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod);
-        markFieldAsKeptWithCompatRule(encodedField, keepClass);
+        if (keepClass) {
+          DexClass holderClass = appInfo.definitionFor(encodedField.field.getHolder());
+          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();
       DexEncodedMethod encodedMethod = appInfo.definitionFor(identifierItem.asDexMethod());
       if (encodedMethod != null) {
-        markMethodAsKeptWithCompatRule(encodedMethod);
+        if (encodedMethod.accessFlags.isStatic() || encodedMethod.accessFlags.isConstructor()) {
+          markDirectStaticOrConstructorMethodAsLive(
+              encodedMethod, KeepReason.reflectiveUseIn(method));
+        } else {
+          markVirtualMethodAsLive(encodedMethod, KeepReason.reflectiveUseIn(method));
+        }
       }
     }
   }
@@ -2420,6 +2422,10 @@
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
       assert method != null;
       assert refinedReceiverType.isSubtypeOf(method.holder, this);
+      if (method.holder.isArrayType()) {
+        assert method.name == dexItemFactory.cloneMethodName;
+        return null;
+      }
       DexClass holder = definitionFor(method.holder);
       if (holder == null || holder.isLibraryClass() || holder.isInterface()) {
         return null;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 16d78a7..a797742 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -61,6 +61,10 @@
     return false;
   }
 
+  public boolean isDueToReflectiveUse() {
+    return false;
+  }
+
   public boolean isDueToProguardCompatibility() {
     return false;
   }
@@ -73,6 +77,14 @@
     return new TargetedBySuper(from);
   }
 
+  public static KeepReason reflectiveUseIn(DexEncodedMethod method) {
+    return new ReflectiveUseFrom(method);
+  }
+
+  public static KeepReason methodHandleReferencedIn(DexEncodedMethod method) {
+    return new MethodHandleReferencedFrom(method);
+  }
+
   private static class DueToKeepRule extends KeepReason {
 
     final ProguardKeepRule keepRule;
@@ -289,4 +301,43 @@
       return enqueuer.getAnnotationGraphNode(holder);
     }
   }
+
+  private static class ReflectiveUseFrom extends BasedOnOtherMethod {
+
+    private ReflectiveUseFrom(DexEncodedMethod method) {
+      super(method);
+    }
+
+    @Override
+    public boolean isDueToReflectiveUse() {
+      return true;
+    }
+
+    @Override
+    public EdgeKind edgeKind() {
+      return EdgeKind.ReflectiveUseFrom;
+    }
+
+    @Override
+    String getKind() {
+      return "reflective use in";
+    }
+  }
+
+  private static class MethodHandleReferencedFrom extends BasedOnOtherMethod {
+
+    private MethodHandleReferencedFrom(DexEncodedMethod method) {
+      super(method);
+    }
+
+    @Override
+    public EdgeKind edgeKind() {
+      return EdgeKind.MethodHandleUseFrom;
+    }
+
+    @Override
+    String getKind() {
+      return "method handle referenced from";
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 543dcc4..71b3d53 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
-import com.google.common.collect.ImmutableSet;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -61,35 +60,23 @@
     boolean value = false;
   }
 
-  public static boolean hasReferencesOutside(
-      AppInfoWithSubtyping appInfo, DexProgramClass clazz, Set<DexType> types) {
-    BooleanBox result = new BooleanBox();
-
-    new MainDexDirectReferenceTracer(appInfo, type -> {
-      if (!types.contains(type)) {
-        DexClass cls = appInfo.definitionFor(type);
-        if (cls != null && !cls.isLibraryClass()) {
-          result.value = true;
-        }
-      }
-    }).run(ImmutableSet.of(clazz.type));
-
-    return result.value;
-  }
-
   public static boolean hasReferencesOutsideFromCode(
       AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
 
     BooleanBox result = new BooleanBox();
 
-    new MainDexDirectReferenceTracer(appInfo, type -> {
-      if (!classes.contains(type)) {
-        DexClass cls = appInfo.definitionFor(type);
-        if (cls != null && !cls.isLibraryClass()) {
-          result.value = true;
-        }
-      }
-    }).runOnCode(method);
+    new MainDexDirectReferenceTracer(
+            appInfo,
+            type -> {
+              DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+              if (baseType.isClassType() && !classes.contains(baseType)) {
+                DexClass cls = appInfo.definitionFor(baseType);
+                if (cls != null && !cls.isLibraryClass()) {
+                  result.value = true;
+                }
+              }
+            })
+        .runOnCode(method);
 
     return result.value;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 163c60b..f9846b8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -5,13 +5,8 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -31,19 +26,6 @@
         }
       };
 
-  public static ProguardKeepRule buildMethodHandleKeepRule(DexClass clazz) {
-    ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
-    builder.setOrigin(proguardCompatOrigin);
-    builder.setType(ProguardKeepRuleType.KEEP);
-    builder.getModifiersBuilder().setAllowsObfuscation(true);
-    builder.getModifiersBuilder().setAllowsOptimization(true);
-    builder.setClassType(
-        clazz.isInterface() ? ProguardClassType.INTERFACE : ProguardClassType.CLASS);
-    builder.setClassNames(
-        ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
-    return builder.build();
-  }
-
   public static ProguardKeepRule buildDefaultInitializerKeepRule(DexClass clazz) {
     ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
     builder.setOrigin(proguardCompatOrigin);
@@ -64,36 +46,6 @@
     return builder.build();
   }
 
-  public static ProguardKeepRule buildFieldKeepRule(
-      DexClass clazz, DexEncodedField field, boolean keepClass) {
-    assert clazz.type == field.field.getHolder();
-    ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
-    builder.setOrigin(proguardCompatOrigin);
-    if (keepClass) {
-      builder.setType(ProguardKeepRuleType.KEEP);
-    } else {
-      builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
-    }
-    builder.getModifiersBuilder().setAllowsObfuscation(true);
-    builder.getModifiersBuilder().setAllowsOptimization(true);
-    builder.getClassAccessFlags().setVisibility(clazz.accessFlags);
-    if (clazz.isInterface()) {
-      builder.setClassType(ProguardClassType.INTERFACE);
-    } else {
-      builder.setClassType(ProguardClassType.CLASS);
-    }
-    builder.setClassNames(
-        ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
-    ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
-    memberRuleBuilder.setRuleType(ProguardMemberType.FIELD);
-    memberRuleBuilder.getAccessFlags().setFlags(field.accessFlags);
-    memberRuleBuilder.setName(
-        IdentifierPatternWithWildcards.withoutWildcards(field.field.name.toString()));
-    memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(field.field.type));
-    builder.getMemberRules().add(memberRuleBuilder.build());
-    return builder.build();
-  }
-
   public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
     // TODO(b/122295241): These generated rules should be linked into the graph, eg, the method
     // using identified reflection should be the source keeping the target alive.
@@ -125,41 +77,6 @@
     return builder.build();
   }
 
-  public static ProguardIdentifierNameStringRule buildIdentifierNameStringRule(DexReference item) {
-    assert item.isDexField() || item.isDexMethod();
-    ProguardIdentifierNameStringRule.Builder builder = ProguardIdentifierNameStringRule.builder();
-    ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
-    DexType holderType;
-    if (item.isDexField()) {
-      DexField field = item.asDexField();
-      holderType = field.getHolder();
-      memberRuleBuilder.setRuleType(ProguardMemberType.FIELD);
-      memberRuleBuilder.setName(
-          IdentifierPatternWithWildcards.withoutWildcards(field.name.toString()));
-      memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(field.type));
-    } else {
-      DexMethod method = item.asDexMethod();
-      holderType = method.getHolder();
-      memberRuleBuilder.setRuleType(ProguardMemberType.METHOD);
-      memberRuleBuilder.setName(
-          IdentifierPatternWithWildcards.withoutWildcards(method.name.toString()));
-      memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(method.proto.returnType));
-      List<ProguardTypeMatcher> arguments = Arrays.stream(method.proto.parameters.values)
-          .map(ProguardTypeMatcher::create)
-          .collect(Collectors.toList());
-      memberRuleBuilder.setArguments(arguments);
-    }
-    if (holderType.isInterface()) {
-      builder.setClassType(ProguardClassType.INTERFACE);
-    } else {
-      builder.setClassType(ProguardClassType.CLASS);
-    }
-    builder.setClassNames(
-        ProguardClassNameList.singletonList(ProguardTypeMatcher.create(holderType)));
-    builder.getMemberRules().add(memberRuleBuilder.build());
-    return builder.build();
-  }
-
   public static ProguardAssumeValuesRule buildAssumeValuesForApiLevel(
       DexItemFactory factory, AndroidApiLevel apiLevel) {
     Origin synthesizedFromApiLevel =
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 46d3a68..a0da9e7 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -843,6 +843,9 @@
   }
 
   private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRule context) {
+    if (type.isVoidType()) {
+      return;
+    }
     if (type.isArrayType()) {
       type = type.toBaseType(application.dexItemFactory);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index fbe8968..7edd536 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -336,15 +336,16 @@
   }
 
   private void markTypeAsPinned(DexType type, AbortReason reason) {
-    if (appInfo.isPinned(type)) {
+    DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    if (!baseType.isClassType() || appInfo.isPinned(baseType)) {
       // We check for the case where the type is pinned according to appInfo.isPinned,
       // so we only need to add it here if it is not the case.
       return;
     }
 
-    DexClass clazz = appInfo.definitionFor(type);
+    DexClass clazz = appInfo.definitionFor(baseType);
     if (clazz != null && clazz.isProgramClass()) {
-      boolean changed = pinnedTypes.add(type);
+      boolean changed = pinnedTypes.add(baseType);
 
       if (Log.ENABLED) {
         if (changed && isMergeCandidate(clazz.asProgramClass(), ImmutableSet.of())) {
@@ -743,10 +744,6 @@
     return false;
   }
 
-  private boolean hasReferencesOutside(DexProgramClass clazz, Set<DexType> types) {
-    return MainDexDirectReferenceTracer.hasReferencesOutside(appInfo, clazz, types);
-  }
-
   private void mergeClassIfPossible(DexProgramClass clazz) {
     if (!mergeCandidates.contains(clazz)) {
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index 0572e88..abd22bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -211,12 +211,16 @@
         return "invoked from lambda created in";
       case ReferencedFrom:
         return "referenced from";
+      case ReflectiveUseFrom:
+        return "reflected from";
       case ReachableFromLiveType:
         return "reachable from";
       case ReferencedInAnnotation:
         return "referenced in annotation";
       case IsLibraryMethod:
         return "defined in library";
+      case MethodHandleUseFrom:
+        return "referenced by method handle";
       default:
         throw new Unreachable("Unexpected edge kind: " + info.edgeKind());
     }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 4921c79..045aa39 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -890,4 +890,12 @@
   public boolean canHaveExceptionTypeBug() {
     return minApiLevel < AndroidApiLevel.Q.getLevel();
   }
+
+  // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
+  // aget instruction. We therefore need to be careful when performing trivial check-cast
+  // elimination of check-cast instructions where the value being cast is the constant null.
+  // See b/123269162.
+  public boolean canHaveArtCheckCastVerifierBug() {
+    return minApiLevel < AndroidApiLevel.J.getLevel();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 3b2b878..1cf61c0 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import java.util.List;
@@ -68,6 +69,7 @@
 
   private static boolean assumeClassesAreEqual(DexProgramClass a) {
     return LambdaRewriter.hasLambdaClassPrefix(a.type)
+        || Java8MethodRewriter.hasJava8MethodRewritePrefix(a.type)
         || InterfaceMethodRewriter.hasDispatchClassSuffix(a.type)
         || a.type.descriptor.toString().equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR);
   }
diff --git a/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
new file mode 100644
index 0000000..4a8b5be
--- /dev/null
+++ b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
@@ -0,0 +1,47 @@
+// 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 classmerging;
+
+import java.lang.reflect.Method;
+
+public class PinnedArrayParameterTypesTest {
+
+  public static void main(String[] args) throws Exception {
+    for (Method method : TestClass.class.getMethods()) {
+      if (method.getName().equals("method")) {
+        Class<?> parameterType = method.getParameterTypes()[0];
+
+        // Should print classmerging.PinnedArrayParameterTypesTest$Interface when
+        // -keepparameternames is used.
+        System.out.println(parameterType.getName());
+
+        method.invoke(null, new Object[]{ new InterfaceImpl[]{ new InterfaceImpl() } });
+        break;
+      }
+    }
+  }
+
+  public interface Interface {
+
+    void foo();
+  }
+
+  public static class InterfaceImpl implements Interface {
+
+    @Override
+    public void foo() {
+      System.out.println("In InterfaceImpl.foo()");
+    }
+  }
+
+  public static class TestClass {
+
+    // This method has been kept explicitly by a keep rule. Therefore, since -keepparameternames is
+    // used, Interface must not be merged into InterfaceImpl.
+    public static void method(Interface[] obj) {
+      obj[0].foo();
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index e3e0006..c75058d 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -43,6 +43,12 @@
 -keep public class classmerging.PinnedParameterTypesTest$TestClass {
   public static void method(...);
 }
+-keep public class classmerging.PinnedArrayParameterTypesTest {
+  public static void main(...);
+}
+-keep public class classmerging.PinnedArrayParameterTypesTest$TestClass {
+  public static void method(...);
+}
 -keep public class classmerging.ProguardFieldMapTest {
   public static void main(...);
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 88d077b..37e4dec 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -3,13 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -17,6 +22,9 @@
     extends TestCompilerBuilder<
         D8Command, Builder, D8TestCompileResult, D8TestRunResult, D8TestBuilder> {
 
+  // Consider an in-order collection of both class and files on the classpath.
+  private List<Class<?>> classpathClasses = new ArrayList<>();
+
   private D8TestBuilder(TestState state, Builder builder) {
     super(state, builder, Backend.DEX);
   }
@@ -34,6 +42,29 @@
   D8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
+    if (!classpathClasses.isEmpty()) {
+      Path cp;
+      try {
+        cp = getState().getNewTempFolder().resolve("cp.jar");
+      } catch (IOException e) {
+        throw builder.getReporter().fatalError("Failed to create temp file for classpath archive");
+      }
+      ArchiveConsumer archiveConsumer = new ArchiveConsumer(cp);
+      for (Class<?> classpathClass : classpathClasses) {
+        try {
+          archiveConsumer.accept(
+              ByteDataView.of(ToolHelper.getClassAsBytes(classpathClass)),
+              DescriptorUtils.javaTypeToDescriptor(classpathClass.getTypeName()),
+              builder.getReporter());
+        } catch (IOException e) {
+          builder
+              .getReporter()
+              .error("Failed to read bytes for classpath class: " + classpathClass.getTypeName());
+        }
+      }
+      archiveConsumer.finished(builder.getReporter());
+      builder.addClasspathFiles(cp);
+    }
     ToolHelper.runD8(builder, optionsConsumer);
     return new D8TestCompileResult(getState(), app.get());
   }
@@ -43,7 +74,8 @@
   }
 
   public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
-    return addClasspathFiles(getFilesForClasses(classes));
+    classpathClasses.addAll(classes);
+    return self();
   }
 
   public D8TestBuilder addClasspathFiles(Path... files) {
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index ea482fa..0fd0354 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -6,9 +6,11 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -99,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<>();
 
@@ -136,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) {
@@ -196,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();
@@ -205,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
@@ -219,4 +230,56 @@
     config.addAll(rules);
     return self();
   }
+  @Override
+  public ProguardTestBuilder addLibraryFiles(Collection<Path> 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
+  public ProguardTestBuilder setProgramConsumer(ProgramConsumer programConsumer) {
+    throw new Unimplemented("No support for program consumer");
+  }
+
+  @Override
+  public ProguardTestBuilder setMinApi(AndroidApiLevel minApiLevel) {
+    throw new Unimplemented("No support for setting min api");
+  }
+
+  @Override
+  public ProguardTestBuilder addMainDexListFiles(Collection<Path> files) {
+    throw new Unimplemented("No support for adding main dex list files");
+  }
+
+  @Override
+  public ProguardTestBuilder setMainDexListConsumer(StringConsumer consumer) {
+    throw new Unimplemented("No support for main dex list consumer");
+  }
+
+  @Override
+  public ProguardTestBuilder setMode(CompilationMode mode) {
+    throw new Unimplemented("No support for setting compilation mode");
+  }
+
+  @Override
+  public ProguardTestBuilder noDesugaring() {
+    throw new Unimplemented("No support for disabling desugaring");
+  }
+
+  @Override
+  public DebugTestConfig debugConfig() {
+    throw new Unimplemented("No support for debug config");
+  }
+
+  @Override
+  public ProguardTestBuilder addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
+    throw new Unimplemented("No support for changing internal options");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index bb5677f..daf5568 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -182,7 +182,11 @@
   }
 
   public R8TestBuilder enableGraphInspector() {
-    CollectingGraphConsumer consumer = new CollectingGraphConsumer(null);
+    return enableGraphInspector(null);
+  }
+
+  public R8TestBuilder enableGraphInspector(GraphConsumer subConsumer) {
+    CollectingGraphConsumer consumer = new CollectingGraphConsumer(subConsumer);
     setKeptGraphConsumer(consumer);
     graphConsumer = consumer;
     return self();
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/RegressionForPrimitiveDefinitionForLookup.java b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
new file mode 100644
index 0000000..b43bb8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
@@ -0,0 +1,43 @@
+// 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;
+
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+class Tester {
+  public int foo() {
+    float[][][] fs = new float[1][2][3];
+    return fs.length;
+  }
+
+  public static void main(String[] args) {
+    System.out.println(new Tester().foo());
+  }
+}
+
+// The DirectoryClasspathProvider asserts lookups are reference types which witnessed the issue.
+public class RegressionForPrimitiveDefinitionForLookup extends TestBase {
+
+  public final Class<Tester> CLASS = Tester.class;
+  public String EXPECTED = StringUtils.lines("1");
+
+  @Test
+  public void testWithArchiveClasspath() throws Exception {
+    testForD8()
+        .addClasspathClasses(CLASS)
+        .addProgramClasses(CLASS)
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithDirectoryClasspath() throws Exception {
+    testForD8()
+        .addClasspathFiles(ToolHelper.getClassPathForTests())
+        .addProgramClasses(CLASS)
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
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..1836a29 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -463,6 +463,29 @@
   }
 
   @Test
+  public void testPinnedArrayParameterTypes() throws Throwable {
+    String main = "classmerging.PinnedArrayParameterTypesTest";
+    Path[] programFiles =
+        new Path[] {
+            CF_DIR.resolve("PinnedArrayParameterTypesTest.class"),
+            CF_DIR.resolve("PinnedArrayParameterTypesTest$Interface.class"),
+            CF_DIR.resolve("PinnedArrayParameterTypesTest$InterfaceImpl.class"),
+            CF_DIR.resolve("PinnedArrayParameterTypesTest$TestClass.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.PinnedArrayParameterTypesTest",
+            "classmerging.PinnedArrayParameterTypesTest$Interface",
+            "classmerging.PinnedArrayParameterTypesTest$InterfaceImpl",
+            "classmerging.PinnedArrayParameterTypesTest$TestClass");
+    runTest(
+        main,
+        programFiles,
+        preservedClassNames::contains,
+        getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"));
+  }
+
+  @Test
   public void testProguardFieldMap() throws Throwable {
     String main = "classmerging.ProguardFieldMapTest";
     Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
index f94d5a5..053ac5c 100644
--- a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.desugar;
 
+import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -26,6 +29,62 @@
         .assertSuccessWithOutput(expectedOutput);
   }
 
+  @Test
+  public void testD8Merge() throws Exception {
+    String jvmOutput = testForJvm()
+        .addTestClasspath()
+        .run(MergeRun.class).getStdOut();
+
+    // See b/123242448
+    Path zip1 = temp.newFile("first.zip").toPath();
+    Path zip2 = temp.newFile("second.zip").toPath();
+    Path zip3 = temp.newFile("third.zip").toPath();
+
+    D8TestCompileResult result1 =
+        testForD8()
+            .setMinApi(AndroidApiLevel.L)
+            .addProgramClasses(MergeRun.class, MergeInputB.class)
+            .compile()
+            .writeToZip(zip1);
+    D8TestCompileResult result2 =
+        testForD8()
+            .setMinApi(AndroidApiLevel.L)
+            .addProgramClasses(MergeInputA.class)
+            .compile()
+            .writeToZip(zip2);
+
+    testForD8()
+        .addProgramFiles(zip1, zip2)
+        .setMinApi(AndroidApiLevel.L)
+        .compile()
+        .run(MergeRun.class)
+        .assertSuccessWithOutput(jvmOutput);
+  }
+
+
+  static class MergeInputA {
+    public void foo() {
+      System.out.println(Integer.hashCode(42));
+      System.out.println(Double.hashCode(42.0));
+    }
+  }
+
+  static class MergeInputB {
+    public void foo() {
+      System.out.println(Integer.hashCode(43));
+      System.out.println(Double.hashCode(43.0));
+    }
+  }
+
+  static class MergeRun {
+    public static void main(String[] args) {
+      MergeInputA a = new MergeInputA();
+      MergeInputB b = new MergeInputB();
+      a.foo();
+      b.foo();
+    }
+  }
+
   static class Java8Methods {
     public static void main(String[] args) {
       byte[] aBytes = new byte[]{42, 1, -1, Byte.MAX_VALUE, Byte.MIN_VALUE};
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10JumboStringTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10JumboStringTest.java
new file mode 100644
index 0000000..365dc58
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10JumboStringTest.java
@@ -0,0 +1,20 @@
+// 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.internal;
+
+import com.android.tools.r8.CompilationMode;
+import org.junit.Test;
+
+public class R8GMSCoreV10JumboStringTest extends R8GMSCoreTreeShakeJarVerificationTest {
+
+  @Test
+  public void verify() throws Exception {
+    buildAndTreeShakeFromDeployJar(
+        CompilationMode.RELEASE,
+        GMSCORE_V10_DIR,
+        false,
+        GMSCORE_V10_MAX_SIZE,
+        options -> options.testing.forceJumboStringProcessing = true);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
new file mode 100644
index 0000000..8cf8b26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
@@ -0,0 +1,188 @@
+// 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.ir.optimize.checkcast;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+/** Regression test for b/123269162. */
+public class TrivialArrayCheckCastTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines(
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    InternalOptions options = new InternalOptions();
+    options.minApiLevel = AndroidApiLevel.I_MR1.getLevel();
+    assert options.canHaveArtCheckCastVerifierBug();
+
+    testForR8(Backend.DEX)
+        .addInnerClasses(TrivialArrayCheckCastTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testBooleanArray();
+      testByteArray();
+      testCharArray();
+      testDoubleArray();
+      testFloatArray();
+      testFloatArrayNested();
+      testIntArray();
+      testLongArray();
+      testObjectArray();
+      testShortArray();
+    }
+
+    @NeverInline
+    private static void testBooleanArray() {
+      boolean[] array = (boolean[]) null;
+      try {
+        boolean value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testByteArray() {
+      byte[] array = (byte[]) null;
+      try {
+        byte value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testCharArray() {
+      char[] array = (char[]) null;
+      try {
+        char value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testDoubleArray() {
+      double[] array = (double[]) null;
+      try {
+        double value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testFloatArray() {
+      float[] array = (float[]) null;
+      try {
+        float value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testFloatArrayNested() {
+      float[][] nestedArray = (float[][]) null;
+      try {
+        float[] array = nestedArray[42];
+        float value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testIntArray() {
+      int[] array = (int[]) null;
+      try {
+        int value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testLongArray() {
+      long[] array = (long[]) null;
+      try {
+        long value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testObjectArray() {
+      Object[] array = (Object[]) null;
+      try {
+        Object value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testShortArray() {
+      short[] array = (short[]) null;
+      try {
+        short value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 6504169..906aee9 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -23,8 +23,6 @@
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.shaking.ProguardKeepRule;
-import com.android.tools.r8.shaking.ProguardKeepRuleType;
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.android.tools.r8.shaking.ProguardMemberType;
 import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.ClassImplementingInterface;
@@ -40,8 +38,6 @@
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
-import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
-import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.io.File;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -277,39 +273,6 @@
       assertEquals(subject.isPresent() && allowObfuscation, subject.isRenamed());
     });
 
-    // Check the Proguard compatibility rules generated.
-    ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
-    parser.parse(proguardCompatibilityRules);
-    ProguardConfiguration configuration = parser.getConfigRawForTesting();
-    if (forceProguardCompatibility) {
-      List<ProguardConfigurationRule> rules = configuration.getRules();
-      assertEquals(2, rules.size());
-      for (ProguardConfigurationRule r : rules) {
-        assertTrue(r instanceof ProguardKeepRule);
-        ProguardKeepRule rule = (ProguardKeepRule) r;
-        assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
-        assertTrue(rule.getModifiers().allowsObfuscation);
-        assertTrue(rule.getModifiers().allowsOptimization);
-        List<ProguardMemberRule> memberRules = rule.getMemberRules();
-        ProguardClassNameList classNames = rule.getClassNames();
-        assertEquals(1, classNames.size());
-        DexType type = classNames.asSpecificDexTypes().get(0);
-        if (type.toSourceString().equals(forNameClass1.getCanonicalName())) {
-          assertEquals(1, memberRules.size());
-          assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
-        } else {
-          assertTrue(type.toSourceString().equals(forNameClass2.getCanonicalName()));
-          // During parsing we add in the default constructor if there are otherwise no single
-          // member rule.
-          assertEquals(1, memberRules.size());
-          assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
-        }
-      }
-    } else {
-      assertEquals(0, configuration.getRules().size());
-    }
-
     if (isRunProguard()) {
       Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
       Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
@@ -376,38 +339,6 @@
     assertTrue(bar.isPresent());
     assertEquals(bar.isPresent() && allowObfuscation, bar.isRenamed());
 
-    // Check the Proguard compatibility rules generated.
-    ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
-    parser.parse(proguardCompatibilityRules);
-    ProguardConfiguration configuration = parser.getConfigRawForTesting();
-    if (forceProguardCompatibility) {
-      List<ProguardConfigurationRule> rules = configuration.getRules();
-      assertEquals(2, rules.size());
-      for (ProguardConfigurationRule r : rules) {
-        assertTrue(r instanceof ProguardKeepRule);
-        ProguardKeepRule rule = (ProguardKeepRule) r;
-        assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
-        assertTrue(rule.getModifiers().allowsObfuscation);
-        assertTrue(rule.getModifiers().allowsOptimization);
-        List<ProguardMemberRule> memberRules = rule.getMemberRules();
-        ProguardClassNameList classNames = rule.getClassNames();
-        assertEquals(1, classNames.size());
-        DexType type = classNames.asSpecificDexTypes().get(0);
-        assertEquals(withMemberClass.getCanonicalName(), type.toSourceString());
-        assertEquals(1, memberRules.size());
-        ProguardMemberRule memberRule = memberRules.iterator().next();
-        if (memberRule.getRuleType() == ProguardMemberType.FIELD) {
-          assertTrue(memberRule.getName().matches("foo"));
-        } else {
-          assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
-          assertTrue(memberRule.getName().matches("bar"));
-        }
-      }
-    } else {
-      assertEquals(0, configuration.getRules().size());
-    }
-
     if (isRunProguard()) {
       Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
       Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
@@ -481,39 +412,6 @@
     assertTrue(f.isPresent());
     assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
 
-    // Check the Proguard compatibility rules generated.
-    ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
-    parser.parse(proguardCompatibilityRules);
-    ProguardConfiguration configuration = parser.getConfigRawForTesting();
-    if (forceProguardCompatibility) {
-      List<ProguardConfigurationRule> rules = configuration.getRules();
-      assertEquals(3, rules.size());
-      Object2BooleanMap<String> keptFields = new Object2BooleanArrayMap<>();
-      for (ProguardConfigurationRule r : rules) {
-        assertTrue(r instanceof ProguardKeepRule);
-        ProguardKeepRule rule = (ProguardKeepRule) r;
-        assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
-        assertTrue(rule.getModifiers().allowsObfuscation);
-        assertTrue(rule.getModifiers().allowsOptimization);
-        List<ProguardMemberRule> memberRules = rule.getMemberRules();
-        ProguardClassNameList classNames = rule.getClassNames();
-        assertEquals(1, classNames.size());
-        DexType type = classNames.asSpecificDexTypes().get(0);
-        assertEquals(withVolatileFields.getCanonicalName(), type.toSourceString());
-        assertEquals(1, memberRules.size());
-        ProguardMemberRule memberRule = memberRules.iterator().next();
-        assertEquals(ProguardMemberType.FIELD, memberRule.getRuleType());
-        keptFields.put(memberRule.getName().toString(), true);
-      }
-      assertEquals(3, keptFields.size());
-      assertTrue(keptFields.containsKey("intField"));
-      assertTrue(keptFields.containsKey("longField"));
-      assertTrue(keptFields.containsKey("objField"));
-    } else {
-      assertEquals(0, configuration.getRules().size());
-    }
-
     if (isRunProguard()) {
       Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
       Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
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
new file mode 100644
index 0000000..e518181
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -0,0 +1,20 @@
+// 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.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByFieldReflectionTest {
+
+  // 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.
+    KeptByFieldReflectionTest obj = new KeptByFieldReflectionTest();
+    System.out.println("got foo: " + KeptByFieldReflectionTest.class.getField("foo").getInt(obj));
+  }
+}
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
new file mode 100644
index 0000000..62dcebe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -0,0 +1,85 @@
+// 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.keptgraph;
+
+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.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+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 KeptByFieldReflectionTestRunner extends TestBase {
+
+  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: 0");
+
+  private final String EXPECTED_WHYAREYOUKEEPING =
+      StringUtils.lines(
+          "int com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.foo",
+          "|- is reflected from:",
+          "|  void com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.main(java.lang.String[])",
+          "|- is referenced in keep rule:",
+          "|  -keep class com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest { public static void main(java.lang.String[]); }");
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public KeptByFieldReflectionTestRunner(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+    FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
+
+    if (backend == Backend.CF) {
+      testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+    }
+
+    WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+    GraphInspector inspector =
+        testForR8(backend)
+            .enableGraphInspector(consumer)
+            .enableInliningAnnotations()
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(CLASS)
+            .run(CLASS)
+            .assertSuccessWithOutput(EXPECTED_STDOUT)
+            .graphInspector();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    consumer.printWhyAreYouKeeping(fooField, new PrintStream(baos));
+    assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+    assertEquals(1, inspector.getRoots().size());
+    QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+    inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+    inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
new file mode 100644
index 0000000..6394237
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
@@ -0,0 +1,20 @@
+// 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.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByMethodReflectionTest {
+
+  public void foo() {
+    System.out.println("called foo");
+  }
+
+  public static void main(String[] args) throws Exception {
+    // Due to b/123210548 the object cannot be created by a reflective newInstance call.
+    KeptByMethodReflectionTest obj = new KeptByMethodReflectionTest();
+    KeptByMethodReflectionTest.class.getMethod("foo").invoke(obj);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
new file mode 100644
index 0000000..3224689
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
@@ -0,0 +1,83 @@
+// 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.keptgraph;
+
+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.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+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 KeptByMethodReflectionTestRunner extends TestBase {
+
+  private static final Class<?> CLASS = KeptByMethodReflectionTest.class;
+  private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+  private final String EXPECTED_STDOUT = StringUtils.lines("called foo");
+
+  private final String EXPECTED_WHYAREYOUKEEPING =
+      StringUtils.lines(
+          "void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.foo()",
+          "|- is reflected from:",
+          "|  void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.main(java.lang.String[])",
+          "|- is referenced in keep rule:",
+          "|  -keep class com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest { public static void main(java.lang.String[]); }");
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public KeptByMethodReflectionTestRunner(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+    MethodReference fooMethod = methodFromMethod(CLASS.getDeclaredMethod("foo"));
+
+    if (backend == Backend.CF) {
+      testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+    }
+
+    WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+    GraphInspector inspector =
+        testForR8(backend)
+            .enableGraphInspector(consumer)
+            .enableInliningAnnotations()
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(CLASS)
+            .run(CLASS)
+            .assertSuccessWithOutput(EXPECTED_STDOUT)
+            .graphInspector();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    consumer.printWhyAreYouKeeping(fooMethod, new PrintStream(baos));
+    assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+    assertEquals(1, inspector.getRoots().size());
+    QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+    inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+    inspector.method(fooMethod).assertRenamed().assertReflectedFrom(mainMethod);
+  }
+}
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/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 940482b..1d53013 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -41,6 +41,8 @@
   public static class EdgeKindPredicate implements Predicate<Set<GraphEdgeInfo>> {
     public static final EdgeKindPredicate keepRule = new EdgeKindPredicate(EdgeKind.KeepRule);
     public static final EdgeKindPredicate invokedFrom = new EdgeKindPredicate(EdgeKind.InvokedFrom);
+    public static final EdgeKindPredicate reflectedFrom =
+        new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
 
     private final EdgeKind edgeKind;
 
@@ -69,6 +71,8 @@
 
     abstract boolean isInvokedFrom(MethodReference method);
 
+    abstract boolean isReflectedFrom(MethodReference method);
+
     abstract boolean isKeptBy(QueryNode node);
 
     abstract String getNodeDescription();
@@ -119,6 +123,19 @@
       return this;
     }
 
+    public QueryNode assertReflectedFrom(MethodReference method) {
+      assertTrue(
+          errorMessage("reflection from " + method.toString(), "none"), isReflectedFrom(method));
+      return this;
+    }
+
+    public QueryNode assertNotReflectedFrom(MethodReference method) {
+      assertFalse(
+          errorMessage("no reflection from " + method.toString(), "reflection"),
+          isReflectedFrom(method));
+      return this;
+    }
+
     public QueryNode assertKeptBy(QueryNode node) {
       assertTrue(
           "Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
@@ -174,6 +191,12 @@
     }
 
     @Override
+    public boolean isReflectedFrom(MethodReference method) {
+      fail("Invalid call to isReflectedFrom on " + getNodeDescription());
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean isKeptBy(QueryNode node) {
       fail("Invalid call to isKeptBy on " + getNodeDescription());
       throw new Unreachable();
@@ -238,6 +261,18 @@
     }
 
     @Override
+    public boolean isReflectedFrom(MethodReference method) {
+      GraphNode sourceMethod = inspector.methods.get(method);
+      if (sourceMethod == null) {
+        return false;
+      }
+      return filterSources(
+              (node, infos) -> node == sourceMethod && EdgeKindPredicate.reflectedFrom.test(infos))
+          .findFirst()
+          .isPresent();
+    }
+
+    @Override
     public boolean isKeptBy(QueryNode node) {
       if (!(node instanceof QueryNodeImpl)) {
         return false;
@@ -356,6 +391,10 @@
     return getQueryNode(methods.get(method), method.toString());
   }
 
+  public QueryNode field(FieldReference field) {
+    return getQueryNode(fields.get(field), field.toString());
+  }
+
   private QueryNode getQueryNode(GraphNode node, String absentString) {
     return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
   }