Merge "Allow storing arbitrary class types in fields with an interface type"
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 61161bb..48c2cde 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -4,12 +4,12 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index a1a2837..a55033a 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
diff --git a/src/main/java/com/android/tools/r8/KeepSubclassesForCodeGeneration.java b/src/main/java/com/android/tools/r8/KeepSubclassesForCodeGeneration.java
new file mode 100644
index 0000000..5f20584
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/KeepSubclassesForCodeGeneration.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2018, 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;
+
+@Keep
+public @interface KeepSubclassesForCodeGeneration {}
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 3687f23..d5f0156 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -15,6 +16,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -115,13 +119,13 @@
 
     @Override
     public boolean registerInstanceFieldWrite(DexField field) {
-      addField(field);
+      addField(field, false);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldRead(DexField field) {
-      addField(field);
+      addField(field, false);
       return false;
     }
 
@@ -133,13 +137,13 @@
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
-      addField(field);
+      addField(field, true);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
-      addField(field);
+      addField(field, true);
       return false;
     }
 
@@ -160,8 +164,15 @@
       return descriptors.contains(type.toDescriptorString());
     }
 
-    private void addField(DexField field) {
+    private void addField(DexField field, boolean isStatic) {
       addType(field.type);
+      DexEncodedField baseField =
+          isStatic
+              ? appInfo.lookupStaticTarget(field.clazz, field)
+              : appInfo.lookupInstanceTarget(field.clazz, field);
+      if (baseField != null && baseField.field.clazz != field.clazz) {
+        field = baseField.field;
+      }
       addType(field.clazz);
       Set<DexField> typeFields = fields.get(field.clazz);
       if (typeFields != null) {
@@ -193,6 +204,14 @@
       for (DexType type : method.method.proto.parameters.values) {
         registerTypeReference(type);
       }
+      for (DexAnnotation annotation : method.annotations.annotations) {
+        if (annotation.annotation.type == appInfo.dexItemFactory.annotationThrows) {
+          DexValueArray dexValues = (DexValueArray) annotation.annotation.elements[0].value;
+          for (DexValue dexValType : dexValues.getValues()) {
+            registerTypeReference(((DexValueType) dexValType).value);
+          }
+        }
+      }
       registerTypeReference(method.method.proto.returnType);
       method.registerCodeReferences(this);
     }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5c82a50..c1664a6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.EnumOrdinalMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e533101..39630f3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.origin.StandardOutOrigin;
diff --git a/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
index c317293..5242510 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
 
diff --git a/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
index 41bd498..edbffbe 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.ClassReference;
diff --git a/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
index 9ae9a5b..b091613 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.FieldReference;
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
index 8c124e5..3c7bda1 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.KeepForSubclassing;
 
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index c0b3f3b..0c838ac 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 public class GraphEdgeInfo {
 
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
index f248978..b99485a 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
 
diff --git a/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
index 05a6e3d..7eb55a8 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.origin.Origin;
diff --git a/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
index 494ddd4..94fe8e7 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.MethodReference;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index f69251b..c76865e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -482,6 +482,12 @@
     enclosingMethod = null;
   }
 
+  public void removeEnclosingMethod(Predicate<EnclosingMethodAttribute> predicate) {
+    if (enclosingMethod != null && predicate.test(enclosingMethod)) {
+      enclosingMethod = null;
+    }
+  }
+
   public void clearInnerClasses() {
     innerClasses.clear();
   }
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 e4f1c8c..012c4a4 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -7,6 +7,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
@@ -21,9 +22,11 @@
 
 public class DirectMappedDexApplication extends DexApplication {
 
+  private final AllClasses allClasses;
   private final ImmutableMap<DexType, DexLibraryClass> libraryClasses;
 
   private DirectMappedDexApplication(ClassNameMapper proguardMap,
+      AllClasses allClasses,
       ProgramClassCollection programClasses,
       ImmutableList<ProgramResourceProvider> programResourceProviders,
       ImmutableMap<DexType, DexLibraryClass> libraryClasses,
@@ -32,6 +35,7 @@
       Timing timing) {
     super(proguardMap, programClasses, programResourceProviders, mainDexList, deadCode,
         dexItemFactory, highestSortingString, timing);
+    this.allClasses = allClasses;
     this.libraryClasses = libraryClasses;
   }
 
@@ -92,17 +96,21 @@
 
   public static class Builder extends DexApplication.Builder<Builder> {
 
+    private final AllClasses allClasses;
     private final List<DexLibraryClass> libraryClasses = new ArrayList<>();
 
     Builder(LazyLoadedDexApplication application) {
       super(application);
       // As a side-effect, this will force-load all classes.
-      Map<DexType, DexClass> allClasses = application.getFullClassMap();
+      this.allClasses = application.loadAllClasses();
+      Map<DexType, DexClass> allClasses = this.allClasses.getClasses();
+      // TODO(120884788): This filter will only add library classes which are not program classes.
       Iterables.filter(allClasses.values(), DexLibraryClass.class).forEach(libraryClasses::add);
     }
 
     private Builder(DirectMappedDexApplication application) {
       super(application);
+      this.allClasses = application.allClasses;
       this.libraryClasses.addAll(application.libraryClasses.values());
     }
 
@@ -116,6 +124,7 @@
       // Rebuild the map. This will fail if keys are not unique.
       return new DirectMappedDexApplication(
           proguardMap,
+          allClasses,
           ProgramClassCollection.create(
               programClasses, ProgramClassCollection::resolveClassConflictImpl),
           ImmutableList.copyOf(programResourceProviders),
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 b648e41..6dea825 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -55,31 +55,67 @@
     return clazz;
   }
 
-  private Map<DexType, DexClass> forceLoadAllClasses() {
-    Map<DexType, DexClass> loaded = new IdentityHashMap<>();
+  static class AllClasses {
+    private Map<DexType, DexClass> libraryClasses;
+    private Map<DexType, DexClass> classpathClasses;
+    private Map<DexType, DexClass> programClasses;
+    private Map<DexType, DexClass> classes;
 
-    // Program classes are supposed to be loaded, but force-loading them is no-op.
-    programClasses.forceLoad(type -> true);
-    programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+    AllClasses(
+        LibraryClassCollection libraryClasses,
+        ClasspathClassCollection classpathClasses,
+        ProgramClassCollection programClasses) {
+      load(libraryClasses, classpathClasses, programClasses);
 
-    if (classpathClasses != null) {
-      classpathClasses.forceLoad(type -> !loaded.containsKey(type));
-      classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+      // Collect loaded classes in the precedence order program classes, class path classes and
+      // library classes.
+      // TODO(b/120884788): Change this.
+      classes = new IdentityHashMap<>();
+      classes.putAll(this.programClasses);
+      if (classpathClasses != null) {
+        classpathClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+      }
+      if (libraryClasses != null) {
+        libraryClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+      }
     }
 
-    if (libraryClasses != null) {
-      libraryClasses.forceLoad(type -> !loaded.containsKey(type));
-      libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+    public Map<DexType, DexClass> getLibraryClasses() {
+      return libraryClasses;
     }
 
-    return loaded;
+    public Map<DexType, DexClass> getClasspathClasses() {
+      return classpathClasses;
+    }
+
+    public Map<DexType, DexClass> getClasses() {
+      return classes;
+    }
+
+    private void load(
+        LibraryClassCollection libraryClasses,
+        ClasspathClassCollection classpathClasses,
+        ProgramClassCollection programClasses) {
+      if (libraryClasses != null) {
+        libraryClasses.forceLoad(type -> true);
+        this.libraryClasses = libraryClasses.getAllClassesInMap();
+      }
+      if (classpathClasses != null) {
+        classpathClasses.forceLoad(type -> true);
+        this.classpathClasses = classpathClasses.getAllClassesInMap();
+      }
+      assert programClasses != null;
+      // Program classes are supposed to be loaded, but force-loading them is no-op.
+      programClasses.forceLoad(type -> true);
+      this.programClasses = programClasses.getAllClassesInMap();
+    }
   }
 
   /**
    * Force load all classes and return type -> class map containing all the classes.
    */
-  public Map<DexType, DexClass> getFullClassMap() {
-    return forceLoadAllClasses();
+  public AllClasses loadAllClasses() {
+    return new AllClasses(libraryClasses, classpathClasses, programClasses);
   }
 
   public static class Builder extends DexApplication.Builder<Builder> {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 0434f66..6ee0f6e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -41,6 +41,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 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.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
@@ -119,6 +120,7 @@
   private final LambdaRewriter lambdaRewriter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final TwrCloseResourceRewriter twrCloseResourceRewriter;
+  private final Java8MethodRewriter java8MethodRewriter;
   private final LambdaMerger lambdaMerger;
   private final ClassInliner classInliner;
   private final ClassStaticizer classStaticizer;
@@ -183,6 +185,8 @@
     this.twrCloseResourceRewriter =
         (options.enableDesugaring && enableTwrCloseResourceDesugaring())
             ? new TwrCloseResourceRewriter(this) : null;
+    this.java8MethodRewriter =
+        options.enableDesugaring ? new Java8MethodRewriter(this) : null;
     this.lambdaMerger =
         options.enableLambdaMerging ? new LambdaMerger(appInfo, options.reporter) : null;
     this.covariantReturnTypeAnnotationTransformer =
@@ -367,6 +371,12 @@
     }
   }
 
+  private void synthesizeJava8UtilityClass(Builder<?> builder) {
+    if (java8MethodRewriter != null) {
+      java8MethodRewriter.synthesizeUtilityClass(builder, options);
+    }
+  }
+
   private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
     if (covariantReturnTypeAnnotationTransformer != null) {
       covariantReturnTypeAnnotationTransformer.process(builder);
@@ -387,6 +397,7 @@
     synthesizeLambdaClasses(builder, executor);
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     synthesizeTwrCloseResourceUtilityClass(builder);
+    synthesizeJava8UtilityClass(builder);
     processCovariantReturnTypeAnnotations(builder);
 
     handleSynthesizedClassMapping(builder);
@@ -573,11 +584,10 @@
 
     printPhase("Interface method desugaring");
     desugarInterfaceMethods(builder, IncludeAllResources, executorService);
-
     printPhase("Twr close resource utility class synthesis");
     synthesizeTwrCloseResourceUtilityClass(builder);
+    synthesizeJava8UtilityClass(builder);
     handleSynthesizedClassMapping(builder);
-
     printPhase("Lambda merging finalization");
     finalizeLambdaMerging(application, feedback, builder, executorService);
 
@@ -1010,6 +1020,9 @@
     if (options.enableDesugaring && enableTryWithResourcesDesugaring()) {
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
     }
+    if (java8MethodRewriter != null) {
+      java8MethodRewriter.desugar(code);
+    }
 
     stringConcatRewriter.desugarStringConcats(method.method, code);
 
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
new file mode 100644
index 0000000..ea1d2c6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -0,0 +1,496 @@
+// Copyright (c) 2018, 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.desugar;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication.Builder;
+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.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter.RewritableMethods.MethodGenerator;
+import com.android.tools.r8.ir.synthetic.TemplateMethodCode;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+
+public final class Java8MethodRewriter {
+  private static final String UTILITY_CLASS_DESCRIPTOR_PREFIX = "L$r8$java8methods$utility";
+  private final Set<DexType> holders = Sets.newConcurrentHashSet();
+  private final IRConverter converter;
+  private final DexItemFactory factory;
+  private final RewritableMethods rewritableMethods;
+
+  private Map<DexMethod, MethodGenerator> methodGenerators = new ConcurrentHashMap<>();
+
+  public Java8MethodRewriter(IRConverter converter) {
+    this.converter = converter;
+    this.factory = converter.appInfo.dexItemFactory;
+    this.rewritableMethods = new RewritableMethods(factory);
+  }
+
+  public void desugar(IRCode code) {
+    InstructionIterator iterator = code.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (!instruction.isInvokeStatic()) {
+        continue;
+      }
+      InvokeStatic invoke = instruction.asInvokeStatic();
+
+      MethodGenerator generator = getMethodGeneratorOrNull(converter, invoke.getInvokedMethod());
+      if (generator == null) {
+        continue;
+      }
+      iterator.replaceCurrentInstruction(
+            new InvokeStatic(generator.generateMethod(factory),
+                invoke.outValue(), invoke.inValues()));
+      methodGenerators.putIfAbsent(generator.generateMethod(factory), generator);
+      holders.add(code.method.method.holder);
+    }
+  }
+
+  private Collection<DexProgramClass> findSynthesizedFrom(Builder<?> builder, DexType holder) {
+    for (DexProgramClass synthesizedClass : builder.getSynthesizedClasses()) {
+      if (holder == synthesizedClass.getType()) {
+        return synthesizedClass.getSynthesizedFrom();
+      }
+    }
+    return null;
+  }
+
+  public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
+    if (holders.isEmpty()) {
+      return;
+    }
+    Set<DexProgramClass> referencingClasses = Sets.newConcurrentHashSet();
+    for (DexType holder : holders) {
+      DexClass definitionFor = converter.appInfo.definitionFor(holder);
+      if (definitionFor == null) {
+        Collection<DexProgramClass> synthesizedFrom = findSynthesizedFrom(builder, holder);
+        assert synthesizedFrom != null;
+        referencingClasses.addAll(synthesizedFrom);
+      } else {
+        referencingClasses.add(definitionFor.asProgramClass());
+      }
+    }
+    MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(
+        Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
+    ClassAccessFlags classAccessFlags =
+        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+
+    for (MethodGenerator generator : methodGenerators.values()) {
+      DexMethod method = generator.generateMethod(factory);
+      TemplateMethodCode code = generator.generateTemplateMethod(options, method);
+      DexEncodedMethod dexEncodedMethod= new DexEncodedMethod(method,
+          flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
+      DexProgramClass utilityClass =
+          new DexProgramClass(
+              method.holder,
+              null,
+              new SynthesizedOrigin("java8 methods utility class", getClass()),
+              classAccessFlags,
+              factory.objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              new DexEncodedMethod[]{dexEncodedMethod},
+              DexEncodedMethod.EMPTY_ARRAY,
+              factory.getSkipNameValidationForTesting(),
+              referencingClasses);
+      code.setUpContext(utilityClass);
+      boolean addToMainDexList = referencingClasses.stream()
+          .anyMatch(clazz -> converter.appInfo.isInMainDexList(clazz.type));
+      converter.optimizeSynthesizedClass(utilityClass);
+      builder.addSynthesizedClass(utilityClass, addToMainDexList);
+    }
+  }
+
+  private MethodGenerator getMethodGeneratorOrNull(IRConverter converter, DexMethod method) {
+    DexMethod original = converter.graphLense().getOriginalMethodSignature(method);
+    assert original != null;
+    return rewritableMethods.getGenerator(
+        original.holder.descriptor, original.name, original.proto);
+  }
+
+
+  private static final class IntegerMethods extends TemplateMethodCode {
+    IntegerMethods(InternalOptions options, DexMethod method, String methodName) {
+      super(options, method, methodName, method.proto.toDescriptorString());
+    }
+
+    public static IntegerMethods hashCodeCode(InternalOptions options, DexMethod method) {
+      return new IntegerMethods(options, method, "hashCodeImpl");
+    }
+
+    public static IntegerMethods maxCode(InternalOptions options, DexMethod method) {
+      return new IntegerMethods(options, method, "maxImpl");
+    }
+
+    public static IntegerMethods minCode(InternalOptions options, DexMethod method) {
+      return new IntegerMethods(options, method, "minImpl");
+    }
+
+    public static IntegerMethods sumCode(InternalOptions options, DexMethod method) {
+      return new IntegerMethods(options, method, "sumImpl");
+    }
+
+    public static int hashCodeImpl(int i) {
+      return Integer.valueOf(i).hashCode();
+    }
+
+    public static int maxImpl(int a, int b) {
+      return java.lang.Math.max(a, b);
+    }
+
+    public static int minImpl(int a, int b) {
+      return java.lang.Math.min(a, b);
+    }
+
+    public static int sumImpl(int a, int b) {
+      return a + b;
+    }
+  }
+
+  private static final class DoubleMethods extends TemplateMethodCode {
+    DoubleMethods(InternalOptions options, DexMethod method, String methodName) {
+      super(options, method, methodName, method.proto.toDescriptorString());
+    }
+
+    public static DoubleMethods hashCodeCode(InternalOptions options, DexMethod method) {
+      return new DoubleMethods(options, method, "hashCodeImpl");
+    }
+
+    public static DoubleMethods maxCode(InternalOptions options, DexMethod method) {
+      return new DoubleMethods(options, method, "maxImpl");
+    }
+
+    public static DoubleMethods minCode(InternalOptions options, DexMethod method) {
+      return new DoubleMethods(options, method, "minImpl");
+    }
+
+    public static DoubleMethods sumCode(InternalOptions options, DexMethod method) {
+      return new DoubleMethods(options, method, "sumImpl");
+    }
+
+    public static DoubleMethods isFiniteCode(InternalOptions options, DexMethod method) {
+      return new DoubleMethods(options, method, "isFiniteImpl");
+    }
+
+    public static int hashCodeImpl(double d) {
+      return Double.valueOf(d).hashCode();
+    }
+
+    public static double maxImpl(double a, double b) {
+      return java.lang.Math.max(a, b);
+    }
+
+    public static double minImpl(double a, double b) {
+      return java.lang.Math.min(a, b);
+    }
+
+    public static double sumImpl(double a, double b) {
+      return a + b;
+    }
+
+    public static boolean isFiniteImpl(double d) {
+      Double boxed = Double.valueOf(d);
+      return !boxed.isInfinite() && !boxed.isNaN();
+    }
+  }
+
+  private static final class FloatMethods extends TemplateMethodCode {
+    FloatMethods(InternalOptions options, DexMethod method, String methodName) {
+      super(options, method, methodName, method.proto.toDescriptorString());
+    }
+
+    public static FloatMethods hashCodeCode(InternalOptions options, DexMethod method) {
+      return new FloatMethods(options, method, "hashCodeImpl");
+    }
+
+    public static FloatMethods maxCode(InternalOptions options, DexMethod method) {
+      return new FloatMethods(options, method, "maxImpl");
+    }
+
+    public static FloatMethods minCode(InternalOptions options, DexMethod method) {
+      return new FloatMethods(options, method, "minImpl");
+    }
+
+    public static FloatMethods sumCode(InternalOptions options, DexMethod method) {
+      return new FloatMethods(options, method, "sumImpl");
+    }
+
+    public static FloatMethods isFiniteCode(InternalOptions options, DexMethod method) {
+      return new FloatMethods(options, method, "isFiniteImpl");
+    }
+
+    public static int hashCodeImpl(float d) {
+      return Float.valueOf(d).hashCode();
+    }
+
+    public static float maxImpl(float a, float b) {
+      return java.lang.Math.max(a, b);
+    }
+
+    public static float minImpl(float a, float b) {
+      return java.lang.Math.min(a, b);
+    }
+
+    public static float sumImpl(float a, float b) {
+      return a + b;
+    }
+
+    public static boolean isFiniteImpl(float d) {
+      Float boxed = Float.valueOf(d);
+      return !boxed.isInfinite() && !boxed.isNaN();
+    }
+  }
+
+  private static final class BooleanMethods extends TemplateMethodCode {
+    BooleanMethods(InternalOptions options, DexMethod method, String methodName) {
+      super(options, method, methodName, method.proto.toDescriptorString());
+    }
+
+    public static BooleanMethods hashCodeCode(InternalOptions options, DexMethod method) {
+      return new BooleanMethods(options, method, "hashCodeImpl");
+    }
+
+    public static BooleanMethods logicalAndCode(InternalOptions options, DexMethod method) {
+      return new BooleanMethods(options, method, "logicalAndImpl");
+    }
+
+    public static BooleanMethods logicalOrCode(InternalOptions options, DexMethod method) {
+      return new BooleanMethods(options, method, "logicalOrImpl");
+    }
+
+    public static BooleanMethods logicalXorCode(InternalOptions options, DexMethod method) {
+      return new BooleanMethods(options, method, "logicalXorImpl");
+    }
+
+    public static int hashCodeImpl(boolean b) {
+      return Boolean.valueOf(b).hashCode();
+    }
+
+    public static boolean logicalAndImpl(boolean a, boolean b) {
+      return a && b;
+    }
+
+    public static boolean logicalOrImpl(boolean a, boolean b) {
+      return a || b;
+    }
+
+    public static boolean logicalXorImpl(boolean a, boolean b) {
+      return a ^ b;
+    }
+  }
+
+  public static final class RewritableMethods {
+    // Map class, method, proto to a generator for creating the code and method.
+    private final Map<DexString, Map<DexString, Map<DexProto, MethodGenerator>>> rewritable;
+
+
+    public RewritableMethods(DexItemFactory factory) {
+      rewritable = new HashMap<>();
+      // Integer
+      DexString clazz = factory.boxedIntDescriptor;
+
+      // int Integer.hashCode(int i)
+      DexString method = factory.createString("hashCode");
+      DexProto proto = factory.createProto(factory.intType, factory.intType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(IntegerMethods::hashCodeCode, clazz, method, proto));
+
+      // int Integer.max(int a, int b)
+      method = factory.createString("max");
+      proto = factory.createProto(factory.intType, factory.intType, factory.intType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(IntegerMethods::maxCode, clazz, method, proto));
+
+      // int Integer.min(int a, int b)
+      method = factory.createString("min");
+      proto = factory.createProto(factory.intType, factory.intType, factory.intType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(IntegerMethods::minCode, clazz, method, proto));
+
+      // int Integer.sum(int a, int b)
+      method = factory.createString("sum");
+      proto = factory.createProto(factory.intType, factory.intType, factory.intType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(IntegerMethods::sumCode, clazz, method, proto));
+
+      // Double
+      clazz = factory.boxedDoubleDescriptor;
+
+      // int Double.hashCode(double d)
+      method = factory.createString("hashCode");
+      proto = factory.createProto(factory.intType, factory.doubleType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(DoubleMethods::hashCodeCode, clazz, method, proto));
+
+      // double Double.max(double a, double b)
+      method = factory.createString("max");
+      proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(DoubleMethods::maxCode, clazz, method, proto));
+
+      // double Double.min(double a, double b)
+      method = factory.createString("min");
+      proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(DoubleMethods::minCode, clazz, method, proto));
+
+      // double Double.sum(double a, double b)
+      method = factory.createString("sum");
+      proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(DoubleMethods::sumCode, clazz, method, proto));
+
+      // boolean Double.isFinite(double a)
+      method = factory.createString("isFinite");
+      proto = factory.createProto(factory.booleanType, factory.doubleType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(DoubleMethods::isFiniteCode, clazz, method, proto));
+
+      // Float
+      clazz = factory.boxedFloatDescriptor;
+
+      // int Float.hashCode(float d)
+      method = factory.createString("hashCode");
+      proto = factory.createProto(factory.intType, factory.floatType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(FloatMethods::hashCodeCode, clazz, method, proto));
+
+      // float Float.max(float a, float b)
+      method = factory.createString("max");
+      proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(FloatMethods::maxCode, clazz, method, proto));
+
+      // float Float.min(float a, float b)
+      method = factory.createString("min");
+      proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(FloatMethods::minCode, clazz, method, proto));
+
+      // float Float.sum(float a, float b)
+      method = factory.createString("sum");
+      proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(FloatMethods::sumCode, clazz, method, proto));
+
+      // boolean Float.isFinite(float a)
+      method = factory.createString("isFinite");
+      proto = factory.createProto(factory.booleanType, factory.floatType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(FloatMethods::isFiniteCode, clazz, method, proto));
+
+      // Boolean
+      clazz = factory.boxedBooleanDescriptor;
+
+      // int Boolean.hashCode(boolean b)
+      method = factory.createString("hashCode");
+      proto = factory.createProto(factory.intType, factory.booleanType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(BooleanMethods::hashCodeCode, clazz, method, proto));
+
+      // boolean Boolean.logicalAnd(boolean a, boolean b)
+      method = factory.createString("logicalAnd");
+      proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(BooleanMethods::logicalAndCode, clazz, method, proto));
+
+      // boolean Boolean.logicalOr(boolean a, boolean b)
+      method = factory.createString("logicalOr");
+      proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(BooleanMethods::logicalOrCode, clazz, method, proto));
+
+      // boolean Boolean.logicalXor(boolean a, boolean b)
+      method = factory.createString("logicalXor");
+      proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
+      addOrGetMethod(clazz, method)
+          .put(proto, new MethodGenerator(BooleanMethods::logicalXorCode, clazz, method, proto));
+    }
+
+    private Map<DexString, Map<DexProto, MethodGenerator>> addOrGetClass(DexString clazz) {
+      return rewritable.computeIfAbsent(clazz, k -> new HashMap<>());
+    }
+
+    private Map<DexProto, MethodGenerator> addOrGetMethod(
+        DexString clazz, DexString method) {
+      return addOrGetClass(clazz).computeIfAbsent(method, k -> new HashMap<>());
+    }
+
+    public MethodGenerator getGenerator(DexString clazz, DexString method, DexProto proto) {
+      Map<DexString, Map<DexProto, MethodGenerator>> classMap = rewritable.get(clazz);
+      if (classMap != null) {
+        Map<DexProto, MethodGenerator> methodMap = classMap.get(method);
+        if (methodMap != null) {
+          return methodMap.get(proto);
+        }
+      }
+      return null;
+    }
+
+    public static class MethodGenerator {
+      private final BiFunction<InternalOptions, DexMethod, TemplateMethodCode> generator;
+      private final DexString clazz;
+      private final DexString method;
+      private final DexProto proto;
+      private DexMethod dexMethod;
+
+      public MethodGenerator(
+          BiFunction<InternalOptions, DexMethod, TemplateMethodCode> generator,
+          DexString clazz, DexString method, DexProto proto) {
+        this.generator = generator;
+        this.clazz = clazz;
+        this.method = method;
+        this.proto = proto;
+      }
+
+      public DexMethod generateMethod(DexItemFactory factory) {
+        if (dexMethod != null) {
+          return dexMethod;
+        }
+        String clazzDescriptor = DescriptorUtils.getSimpleClassNameFromDescriptor(clazz.toString());
+        String postFix = "$" + clazzDescriptor + "$" + method + "$" + proto.shorty.toString();
+        DexType clazz = factory.createType(UTILITY_CLASS_DESCRIPTOR_PREFIX + postFix + ";");
+        dexMethod = factory.createMethod(clazz, proto, method);
+        return dexMethod;
+      }
+
+      public TemplateMethodCode generateTemplateMethod(InternalOptions options, DexMethod method) {
+        return generator.apply(options, method);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/TemplateMethodCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/TemplateMethodCode.java
index fdca28c..5162b6f 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/TemplateMethodCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/TemplateMethodCode.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.synthetic;
 
+import com.android.tools.r8.KeepSubclassesForCodeGeneration;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -17,6 +18,7 @@
 import java.util.function.BiFunction;
 
 // Source code representing code of a method generated based on a template.
+@KeepSubclassesForCodeGeneration
 public abstract class TemplateMethodCode extends JarCode {
   private final String templateMethodName;
   private final String templateMethodDesc;
diff --git a/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java b/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java
index 774a93a..27bdd95 100644
--- a/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graphinfo.GraphConsumer;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
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 6a90323..95f077d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -11,6 +11,15 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
@@ -37,15 +46,6 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
-import com.android.tools.r8.graphinfo.AnnotationGraphNode;
-import com.android.tools.r8.graphinfo.ClassGraphNode;
-import com.android.tools.r8.graphinfo.FieldGraphNode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
-import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.graphinfo.MethodGraphNode;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke.Type;
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 82737a6..16d78a7 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
 
 // TODO(herhut): Canonicalize reason objects.
 public abstract class KeepReason {
@@ -50,7 +50,7 @@
   }
 
   public static KeepReason fieldReferencedIn(DexEncodedMethod method) {
-    return new ReferenedFrom(method);
+    return new ReferencedFrom(method);
   }
 
   public static KeepReason referencedInAnnotation(DexItem holder) {
@@ -219,9 +219,9 @@
     }
   }
 
-  private static class ReferenedFrom extends BasedOnOtherMethod {
+  private static class ReferencedFrom extends BasedOnOtherMethod {
 
-    private ReferenedFrom(DexEncodedMethod method) {
+    private ReferencedFrom(DexEncodedMethod method) {
       super(method);
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index c012898..6cf0da0 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,8 +8,10 @@
 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.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
@@ -102,15 +104,44 @@
         clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
         clazz.setStaticFields(reachableFields(clazz.staticFields()));
         clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
+        clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
         usagePrinter.visited();
       }
     }
     return newClasses;
   }
 
+  private boolean isAttributeReferencingPrunedItem(EnclosingMethodAttribute attr) {
+    return
+        (attr.getEnclosingClass() != null
+            && !appInfo.liveTypes.contains(attr.getEnclosingClass()))
+        || (attr.getEnclosingMethod() != null
+            && !appInfo.liveMethods.contains(attr.getEnclosingMethod()));
+  }
+
   private boolean isAttributeReferencingPrunedType(InnerClassAttribute attr) {
-    return (attr.getInner() != null && !appInfo.liveTypes.contains(attr.getInner()))
-        || (attr.getOuter() != null && !appInfo.liveTypes.contains(attr.getOuter()));
+    if (!appInfo.liveTypes.contains(attr.getInner())) {
+      return true;
+    }
+    DexType context = attr.getOuter();
+    if (context == null) {
+      DexClass inner = appInfo.definitionFor(attr.getInner());
+      if (inner != null && inner.getEnclosingMethod() != null) {
+        EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethod();
+        if (enclosingMethodAttribute.getEnclosingClass() != null) {
+          context = enclosingMethodAttribute.getEnclosingClass();
+        } else {
+          DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
+          if (!appInfo.liveMethods.contains(enclosingMethod)) {
+            // EnclosingMethodAttribute will be pruned as it references the pruned method.
+            // Hence, removal of the current InnerClassAttribute too.
+            return true;
+          }
+          context = enclosingMethod.getHolder();
+        }
+      }
+    }
+    return context == null || !appInfo.liveTypes.contains(context);
   }
 
   private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
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 11f2131..0572e88 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -4,14 +4,14 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graphinfo.ClassGraphNode;
-import com.android.tools.r8.graphinfo.FieldGraphNode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
-import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.graphinfo.MethodGraphNode;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 0ebfb9d..33c1bf3 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -134,6 +135,18 @@
     return loadedClasses;
   }
 
+  public Map<DexType, DexClass> getAllClassesInMap() {
+    if (classProvider.get() != null) {
+      throw new Unreachable("Getting all classes from not fully loaded collection.");
+    }
+    ImmutableMap.Builder<DexType, DexClass> builder = ImmutableMap.builder();
+    // This is fully loaded, so the class map will no longer change.
+    for (Map.Entry<DexType, Supplier<T>> entry : classes.entrySet()) {
+      builder.put(entry.getKey(), entry.getValue().get());
+    }
+    return builder.build();
+  }
+
   public Iterable<DexType> getAllTypes() {
     return classes.keySet();
   }
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 388cac3..90b9e18 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -14,11 +14,11 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfiguration;
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 4b987e8..61b2b14 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -4,6 +4,8 @@
 
 -keep @com.android.tools.r8.Keep class * { public *; }
 -keep @com.android.tools.r8.KeepForSubclassing class * { public *; protected *; }
+-keep class * extends @com.android.tools.r8.KeepSubclassesForCodeGeneration * { public *; }
+
 
 -keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); }
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
index 6d0ef5a..c5e44a3 100644
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -49,13 +49,16 @@
     Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
     FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
 
+    // It is important to place the api usage sample jar after the current classpath because we want
+    // to find D8/R8 classes before the ones in the jar, otherwise renamed classes and fields cannot
+    // be found.
+    String classPath = System.getProperty("java.class.path") + File.pathSeparator + jar.toString();
     List<String> command =
         ImmutableList.<String>builder()
             .addAll(
                 ImmutableList.of(
                     ToolHelper.getJavaExecutable(),
-                    "-cp",
-                    jar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+                    "-cp", classPath,
                     main,
                     // Compiler arguments.
                     "--output",
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index c8d6bd9..19e54bb 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -209,6 +209,18 @@
   }
 
   @Override
+  public ProguardTestBuilder addKeepRuleFiles(List<Path> files) {
+    try {
+      for (Path file : files) {
+        config.addAll(FileUtils.readAllLines(file));
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return self();
+  }
+
+  @Override
   public ProguardTestBuilder addKeepRules(Collection<String> rules) {
     config.addAll(rules);
     return self();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 8c4f682..2c72ec7 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -6,10 +6,11 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.TestBase.R8Mode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -74,6 +75,12 @@
   }
 
   @Override
+  public R8TestBuilder addKeepRuleFiles(List<Path> files) {
+    builder.addProguardConfigurationFiles(files);
+    return self();
+  }
+
+  @Override
   public R8TestBuilder addKeepRules(Collection<String> rules) {
     builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index e7e91e0..42b56ca 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -7,12 +7,12 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 public abstract class TestShrinkerBuilder<
         C extends BaseCompilerCommand,
@@ -30,8 +30,10 @@
 
   public abstract T noMinification();
 
-  public T addKeepRules(Path path) throws IOException {
-    return addKeepRules(FileUtils.readAllLines(path));
+  public abstract T addKeepRuleFiles(List<Path> files);
+
+  public T addKeepRuleFiles(Path... files) throws IOException {
+    return addKeepRuleFiles(Arrays.asList(files));
   }
 
   public abstract T addKeepRules(Collection<String> rules);
diff --git a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
new file mode 100644
index 0000000..8139694
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2018, 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.desugar;
+
+import com.android.tools.r8.TestBase;
+import org.junit.Before;
+import org.junit.Test;
+
+public class Java8MethodsTest extends TestBase {
+  static String expectedOutput = "";
+
+  @Before
+  public void testJvm() throws Exception {
+    expectedOutput = testForJvm()
+        .addTestClasspath()
+        .run(Java8Methods.class).getStdOut();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8()
+        .addProgramClasses(Java8Methods.class)
+        .run(Java8Methods.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class Java8Methods {
+    public static void main(String[] args) {
+      int[] aInts = new int[]{42, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+      int[] bInts = new int[]{43, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+      for (int aInt : aInts) {
+        System.out.println(Integer.hashCode(aInt));
+        for (int bInt : bInts) {
+          System.out.println(Integer.max(aInt, bInt));
+          System.out.println(Integer.min(aInt, bInt));
+          System.out.println(Integer.sum(aInt, bInt));
+        }
+      }
+
+      double[] aDoubles = new double[]{42.0, 1.1, -1.1,  Double.MAX_VALUE, Double.MIN_NORMAL,
+          Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
+      double[] bDoubles = new double[]{43, 1.2, -1.3, Double.MAX_VALUE, Double.MIN_NORMAL,
+          Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
+      for (double aDouble : aDoubles) {
+        System.out.println(Double.hashCode(aDouble));
+        System.out.println(Double.isFinite(aDouble));
+        for (double bDouble : bDoubles) {
+          System.out.println(Double.max(aDouble, bDouble));
+          System.out.println(Double.min(aDouble, bDouble));
+          System.out.println(Double.sum(aDouble, bDouble));
+        }
+      }
+
+      // Float.MAX_VALUE/MIN_VALUE printed values differs between jvm and art on some versions,
+      // e.g., 1.17549435E-38 on jvm, 1.1754944E-38 on art
+      float[] aFloats = new float[]{42.0f, 1.1f, -1.1f,  Float.MAX_VALUE, Float.MIN_NORMAL,
+          Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY};
+      float[] bFloats = new float[]{43, 1.2f, -1.3f, Float.MAX_VALUE, Float.MIN_NORMAL,
+          Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY};
+      for (float aFloat : aFloats) {
+        System.out.println(Float.hashCode(aFloat));
+        System.out.println(Float.isFinite(aFloat));
+        for (float bFloat : bFloats) {
+          // Print comparison, since Max/Min printed values differs between jvm and art
+          System.out.println(Float.max(aFloat, bFloat) == aFloat);
+          System.out.println(Float.min(aFloat, bFloat) == aFloat);
+          // Compare to the calculated sum, again, Max/Min values may differ
+          System.out.println(Float.sum(aFloat, bFloat) == (aFloat + bFloat));
+        }
+      }
+
+      for (boolean aBoolean : new boolean[]{true, false}) {
+        System.out.println(aBoolean);
+        for (boolean bBoolean : new boolean[]{true, false}) {
+          System.out.println(Boolean.logicalAnd(aBoolean, bBoolean));
+          System.out.println(Boolean.logicalOr(aBoolean, bBoolean));
+          System.out.println(Boolean.logicalXor(aBoolean, bBoolean));
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 9cc69fa..54f9df3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -263,7 +263,7 @@
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     TestRunResult result =
         builder
@@ -284,7 +284,7 @@
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     TestRunResult result =
         builder
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index b75e4ee..5a9ca75 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -124,7 +124,7 @@
       "Inner",
       "Inner"
   );
-  private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTE = StringUtils.lines(
+  private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTES = StringUtils.lines(
       "Local_t03",
       "InnerLocal",
       "$",
@@ -211,7 +211,7 @@
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     TestRunResult result =
         builder
@@ -231,12 +231,12 @@
         .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
         // See b/119471127: some old VMs are not resilient to broken attributes.
         // Comment out the following line to reproduce b/120130435
-        // then use OUTPUT_WITH_SHRUNK_ATTRIBUTE
+        // then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
         .addKeepRules("-keep,allowobfuscation class **.Outer*")
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     R8TestRunResult result =
         builder
diff --git a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
index 3eed543..0acd733 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
diff --git a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
index c14cef5..4e121a8 100644
--- a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
@@ -49,6 +49,8 @@
   private final boolean enableMinification;
   private Collection<Path> classPaths;
   private static final String JAVA_OUTPUT = "-Returned-null-" + System.lineSeparator();
+  private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTES =
+      "com.android.tools.r8.shaking.GetNameClass$1" + System.lineSeparator();
   private static final Class<?> MAIN = GetNameMain.class;
 
   @Parameterized.Parameters(name = "Backend: {0} minification: {1}")
@@ -88,18 +90,10 @@
         .addKeepRules("-keep class **.GetName*")
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
 
     R8TestRunResult result = builder.run(MAIN);
-    if (backend == Backend.DEX) {
-      if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)
-          && ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
-        result.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-        return;
-      }
-    }
-
-    result.assertSuccessWithOutput(JAVA_OUTPUT);
+    result.assertSuccessWithOutput(OUTPUT_WITH_SHRUNK_ATTRIBUTES);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java
index 689d3b6..35083c7 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java
@@ -19,6 +19,9 @@
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 class Main {
 
@@ -37,9 +40,19 @@
   }
 }
 
+@RunWith(Parameterized.class)
 public class KeptMethodTest extends TestBase {
 
-  final Backend backend = Backend.DEX;
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public KeptMethodTest(Backend backend) {
+    this.backend = backend;
+  }
 
   @Test
   public void testKeptMethod()
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index 48cdb05..95963a0 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -48,7 +48,7 @@
     Path flagToKeepTestRunner = Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules.txt");
     R8TestBuilder builder = testForR8(backend)
         .addProgramFiles(MOCKITO_INTERFACE_JAR)
-        .addKeepRules(flagToKeepTestRunner);
+        .addKeepRuleFiles(flagToKeepTestRunner);
     if (!minify) {
       builder.noMinification();
     }
@@ -65,7 +65,7 @@
         Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules-conditional-on-mock.txt");
     R8TestBuilder builder = testForR8(backend)
         .addProgramFiles(MOCKITO_INTERFACE_JAR)
-        .addKeepRules(flagToKeepInterfaceConditionally);
+        .addKeepRuleFiles(flagToKeepInterfaceConditionally);
     if (!minify) {
       builder.noMinification();
     }
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 454d1f5..b138e50 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
@@ -4,11 +4,11 @@
 package com.android.tools.r8.utils.graphinspector;
 
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graphinfo.ClassGraphNode;
-import com.android.tools.r8.graphinfo.FieldGraphNode;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphNode;
-import com.android.tools.r8.graphinfo.MethodGraphNode;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;