Merge "Rewrite ClassStaticizerTest with new test builder."
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 00df99e..905e949 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -176,6 +176,9 @@
       AppInfo appInfo = new AppInfo(app);
       app = optimize(app, appInfo, options, timing, executor);
 
+      // Close any internal archive providers now the application is fully processed.
+      inputApp.closeInternalArchiveProviders();
+
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 328d524..3ed662f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -253,6 +253,9 @@
       DexApplication application =
           new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
 
+      // Now that the dex-application is fully loaded, close any internal archive providers.
+      inputApp.closeInternalArchiveProviders();
+
       AppView<AppInfoWithSubtyping> appView =
           new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
       RootSet rootSet;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index a217bc9..ec9b186 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -668,6 +668,7 @@
                   new ForwardMethodSourceCode(
                       accessFlags.isStatic() ? null : holder.type,
                       newMethod,
+                      newMethod,
                       accessFlags.isStatic() ? null : method.holder,
                       method,
                       type,
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index d68cb7d..27e91ef 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -84,13 +85,15 @@
 
   public static class Builder {
 
-    protected Builder() {
-    }
+    protected Builder() {}
 
     protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
     protected final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
 
+    private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
+    private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+
     public void map(DexType from, DexType to) {
       typeMap.put(from, to);
     }
@@ -103,6 +106,16 @@
       fieldMap.put(from, to);
     }
 
+    public void move(DexMethod from, DexMethod to) {
+      map(from, to);
+      originalMethodSignatures.put(to, from);
+    }
+
+    public void move(DexField from, DexField to) {
+      fieldMap.put(from, to);
+      originalFieldSignatures.put(to, from);
+    }
+
     public GraphLense build(DexItemFactory dexItemFactory) {
       return build(dexItemFactory, new IdentityGraphLense());
     }
@@ -112,7 +125,13 @@
         return previousLense;
       }
       return new NestedGraphLense(
-          typeMap, methodMap, fieldMap, null, null, previousLense, dexItemFactory);
+          typeMap,
+          methodMap,
+          fieldMap,
+          originalFieldSignatures,
+          originalMethodSignatures,
+          previousLense,
+          dexItemFactory);
     }
 
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 0ca4f16..d2a233b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -120,6 +120,7 @@
                 new ForwardMethodSourceCode(
                     clazz.type,
                     newMethod,
+                    newMethod,
                     null /* static method */,
                     rewriter.defaultAsMethodOfCompanionClass(method),
                     Invoke.Type.STATIC,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index f963581..955abf0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -170,6 +170,7 @@
                     new ForwardMethodSourceCode(
                         clazz.type,
                         newMethod,
+                        newMethod,
                         method.method.holder,
                         method.method,
                         Invoke.Type.VIRTUAL,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index f989cc9..6ba9b6a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -247,7 +247,13 @@
               new SynthesizedCode(
                   callerPosition ->
                       new ForwardMethodSourceCode(
-                          null, newMethod, null, origMethod, Type.STATIC, callerPosition)));
+                          null,
+                          newMethod,
+                          newMethod,
+                          null,
+                          origMethod,
+                          Type.STATIC,
+                          callerPosition)));
       newEncodedMethod.getMutableOptimizationInfo().markNeverInline();
       dispatchMethods.add(newEncodedMethod);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index c1cf5b5..bad5456 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -97,6 +97,7 @@
     if (target.getOptimizationInfo().forceInline()
         || (inliner.appView.appInfo().hasLiveness()
             && inliner.appView.withLiveness().appInfo().forceInline.contains(target.method))) {
+      assert !appView.appInfo().neverInline.contains(target.method);
       return Reason.FORCE;
     }
     if (inliner.appView.appInfo().hasLiveness()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index d83c550..eb42723 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -190,7 +189,7 @@
     }
   }
 
-  private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
+  private boolean isClassEligible(AppInfoWithLiveness appInfo, DexClass clazz) {
     Boolean eligible = knownClasses.get(clazz);
     if (eligible == null) {
       Boolean computed = computeClassEligible(appInfo, clazz);
@@ -205,9 +204,12 @@
   //   - is not an abstract class or interface
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
-  private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
-    if (clazz == null || clazz.isLibraryClass() ||
-        clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
+  private boolean computeClassEligible(AppInfoWithLiveness appInfo, DexClass clazz) {
+    if (clazz == null
+        || clazz.isLibraryClass()
+        || clazz.accessFlags.isAbstract()
+        || clazz.accessFlags.isInterface()
+        || appInfo.neverClassInline.contains(clazz.type)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
index b8e79cd..37341fb 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
@@ -27,22 +27,32 @@
   public ForwardMethodSourceCode(
       DexType receiver,
       DexMethod method,
+      DexMethod originalMethod,
       DexType targetReceiver,
       DexMethod target,
       Type invokeType,
       Position callerPosition) {
-    this(receiver, method, targetReceiver, target, invokeType, callerPosition, false);
+    this(
+        receiver,
+        method,
+        originalMethod,
+        targetReceiver,
+        target,
+        invokeType,
+        callerPosition,
+        false);
   }
 
   public ForwardMethodSourceCode(
       DexType receiver,
       DexMethod method,
+      DexMethod originalMethod,
       DexType targetReceiver,
       DexMethod target,
       Type invokeType,
       Position callerPosition,
       boolean castResult) {
-    super(receiver, method, callerPosition);
+    super(receiver, method, callerPosition, originalMethod);
     assert (targetReceiver == null) == (invokeType == Invoke.Type.STATIC);
 
     this.target = target;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index fd04882..88bbc6c 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -51,6 +51,11 @@
   private final Position position;
 
   protected SyntheticSourceCode(DexType receiver, DexMethod method, Position callerPosition) {
+    this(receiver, method, callerPosition, method);
+  }
+
+  protected SyntheticSourceCode(
+      DexType receiver, DexMethod method, Position callerPosition, DexMethod originalMethod) {
     assert method != null;
     this.receiver = receiver;
     this.method = method;
@@ -67,7 +72,7 @@
       this.paramRegisters[i] = nextRegister(ValueType.fromDexType(params[i]));
     }
 
-    position = Position.synthetic(0, method, callerPosition);
+    position = Position.synthetic(0, originalMethod, callerPosition);
   }
 
   protected final void add(Consumer<IRBuilder> constructor) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
new file mode 100644
index 0000000..e7dc76c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
@@ -0,0 +1,105 @@
+// 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.shaking;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ClassInlineRule extends ProguardConfigurationRule {
+
+  public enum Type {
+    NEVER
+  }
+
+  public static class Builder extends ProguardConfigurationRule.Builder<ClassInlineRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    Type type;
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    public Builder setType(Type type) {
+      this.type = type;
+      return this;
+    }
+
+    @Override
+    public ClassInlineRule build() {
+      return new ClassInlineRule(
+          origin,
+          getPosition(),
+          source,
+          classAnnotation,
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          inheritanceAnnotation,
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules,
+          type);
+    }
+  }
+
+  private final Type type;
+
+  protected ClassInlineRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules,
+      Type type) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotation,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotation,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+    this.type = type;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  @Override
+  String typeString() {
+    switch (type) {
+      case NEVER:
+        return "neverclassinline";
+    }
+    throw new Unreachable("Unknown class inline type " + type);
+  }
+}
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 06df4a9..31771a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetBuilder.IfRuleEvaluator;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -1349,9 +1350,12 @@
         if (numOfLiveItemsAfterProcessing > numOfLiveItems) {
           RootSetBuilder consequentSetBuilder =
               new RootSetBuilder(appView, rootSet.ifRules, options);
-          ConsequentRootSet consequentRootSet = consequentSetBuilder.runForIfRules(
-              executorService, liveTypes, liveMethods.getItems(), liveFields.getItems());
+          IfRuleEvaluator ifRuleEvaluator =
+              consequentSetBuilder.getIfRuleEvaluator(
+                  liveMethods.getItems(), liveFields.getItems(), executorService);
+          ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
           enqueueRootItems(consequentRootSet.noShrinking);
+          rootSet.neverInline.addAll(consequentRootSet.neverInline);
           rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
           rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
           rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
@@ -1839,6 +1843,10 @@
      */
     public final Set<DexMethod> neverInline;
     /**
+     * All types that *must* never be inlined due to a configuration directive (testing only).
+     */
+    public final Set<DexType> neverClassInline;
+    /**
      * All types that *must* never be merged due to a configuration directive (testing only).
      */
     public final Set<DexType> neverMerge;
@@ -1903,6 +1911,7 @@
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
       this.forceInline = enqueuer.rootSet.forceInline;
       this.neverInline = enqueuer.rootSet.neverInline;
+      this.neverClassInline = enqueuer.rootSet.neverClassInline;
       this.neverMerge = enqueuer.rootSet.neverMerge;
       this.identifierNameStrings = joinIdentifierNameStrings(
           enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
@@ -1947,6 +1956,7 @@
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = previous.forceInline;
       this.neverInline = previous.neverInline;
+      this.neverClassInline = previous.neverClassInline;
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings = previous.identifierNameStrings;
       this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
@@ -2005,6 +2015,7 @@
       assert lense.assertDefinitionNotModified(
           previous.neverMerge.stream().map(this::definitionFor).filter(Objects::nonNull)
               .collect(Collectors.toList()));
+      this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings =
           lense.rewriteReferencesConservatively(previous.identifierNameStrings);
@@ -2050,6 +2061,7 @@
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = previous.forceInline;
       this.neverInline = previous.neverInline;
+      this.neverClassInline = previous.neverClassInline;
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings = previous.identifierNameStrings;
       this.prunedTypes = previous.prunedTypes;
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index f263bd6..3f255f2 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -42,7 +42,7 @@
 
   private final Type type;
 
-  private InlineRule(
+  protected InlineRule(
       Origin origin,
       Position position,
       String source,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 1d970a3..1bbebe0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -339,7 +339,7 @@
     builder.append(' ');
     classNames.writeTo(builder);
     if (hasInheritanceClassName()) {
-      builder.append(inheritanceIsExtends ? "extends" : "implements");
+      builder.append(' ').append(inheritanceIsExtends ? "extends" : "implements");
       StringUtils.appendNonEmpty(builder, "@", inheritanceAnnotation, null);
       builder.append(' ');
       builder.append(inheritanceClassName);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index ea20704..2ab9308 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.position.TextRange;
-import com.android.tools.r8.shaking.InlineRule.Type;
 import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
@@ -353,16 +352,19 @@
       } else if (acceptString("packageobfuscationdictionary")) {
         configurationBuilder.setPackageObfuscationDictionary(parseFileName(false));
       } else if (acceptString("alwaysinline")) {
-        InlineRule rule = parseInlineRule(Type.ALWAYS, optionStart);
+        InlineRule rule = parseInlineRule(InlineRule.Type.ALWAYS, optionStart);
         configurationBuilder.addRule(rule);
       } else if (allowTestOptions && acceptString("forceinline")) {
-        InlineRule rule = parseInlineRule(Type.FORCE, optionStart);
+        InlineRule rule = parseInlineRule(InlineRule.Type.FORCE, optionStart);
         configurationBuilder.addRule(rule);
         // Insert a matching -checkdiscard rule to ensure force inlining happens.
         ProguardCheckDiscardRule ruled = rule.asProguardCheckDiscardRule();
         configurationBuilder.addRule(ruled);
       } else if (allowTestOptions && acceptString("neverinline")) {
-        InlineRule rule = parseInlineRule(Type.NEVER, optionStart);
+        InlineRule rule = parseInlineRule(InlineRule.Type.NEVER, optionStart);
+        configurationBuilder.addRule(rule);
+      } else if (allowTestOptions && acceptString("neverclassinline")) {
+        ClassInlineRule rule = parseClassInlineRule(ClassInlineRule.Type.NEVER, optionStart);
         configurationBuilder.addRule(rule);
       } else if (allowTestOptions && acceptString("nevermerge")) {
         ClassMergingRule rule = parseClassMergingRule(ClassMergingRule.Type.NEVER, optionStart);
@@ -597,6 +599,17 @@
       return keepRuleBuilder.build();
     }
 
+    private ClassInlineRule parseClassInlineRule(ClassInlineRule.Type type, Position start)
+        throws ProguardRuleParserException {
+      ClassInlineRule.Builder keepRuleBuilder =
+          ClassInlineRule.builder().setOrigin(origin).setStart(start).setType(type);
+      parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
+      return keepRuleBuilder.build();
+    }
+
     private ClassMergingRule parseClassMergingRule(ClassMergingRule.Type type, Position start)
         throws ProguardRuleParserException {
       ClassMergingRule.Builder keepRuleBuilder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 6d6d943..3011951 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -87,6 +87,52 @@
         subsequentRule.materialize());
   }
 
+  /**
+   * Consider the following rule, which requests that class Y should be kept if the method X.m() is
+   * in the final output.
+   *
+   * <pre>
+   * -if class X {
+   *   public void m();
+   * }
+   * -keep class Y
+   * </pre>
+   *
+   * When the {@link Enqueuer} finds that the method X.m() is reachable, it applies the subsequent
+   * keep rule of the -if rule. Thus, Y will be marked as pinned, which guarantees, for example,
+   * that it will not be merged into another class by the vertical class merger.
+   *
+   * <p>However, when the {@link Enqueuer} runs for the second time, it is important that X.m() has
+   * not been inlined into another method Z.z(), because that would mean that Z.z() now relies on
+   * the presence of Y, meanwhile Y will not be kept because X.m() is no longer present.
+   *
+   * <p>Therefore, each time the subsequent rule of an -if rule is applied, we also apply a
+   * -neverinline rule for the condition of the -if rule.
+   */
+  protected InlineRule neverInlineRuleForCondition() {
+    if (getMemberRules() == null || getMemberRules().isEmpty()) {
+      return null;
+    }
+    return new InlineRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
+        getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        getMemberRules().stream()
+            .filter(rule -> rule.getRuleType().includesMethods())
+            .map(ProguardMemberRule::materialize)
+            .collect(Collectors.toList()),
+        InlineRule.Type.NEVER);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardIfRule)) {
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 df72e96..c59081e 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -66,6 +66,7 @@
   private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+  private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
   private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking =
       new IdentityHashMap<>();
@@ -180,6 +181,10 @@
         }
       } else if (rule instanceof InlineRule) {
         markMatchingMethods(clazz, memberKeepRules, rule, null);
+      } else if (rule instanceof ClassInlineRule) {
+        if (allRulesSatisfied(memberKeepRules, clazz)) {
+          markClass(clazz, rule);
+        }
       } else if (rule instanceof ProguardAssumeValuesRule) {
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
         markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
@@ -250,6 +255,7 @@
         alwaysInline,
         forceInline,
         neverInline,
+        neverClassInline,
         neverMerge,
         noSideEffects,
         assumedValues,
@@ -258,55 +264,15 @@
         ifRules);
   }
 
-  ConsequentRootSet runForIfRules(
-      ExecutorService executorService,
-      Set<DexType> liveTypes,
+  IfRuleEvaluator getIfRuleEvaluator(
       Set<DexEncodedMethod> liveMethods,
-      Set<DexEncodedField> liveFields) throws ExecutionException {
-    application.timing.begin("Find consequent items for -if rules...");
-    try {
-      if (rules != null) {
-        IfRuleEvaluator evaluator =
-            new IfRuleEvaluator(liveTypes, liveMethods, liveFields, executorService);
-        for (ProguardConfigurationRule rule : rules) {
-          assert rule instanceof ProguardIfRule;
-          ProguardIfRule ifRule = (ProguardIfRule) rule;
-          // Depending on which types that trigger the -if rule, the application of the subsequent
-          // -keep rule may vary (due to back references). So, we need to try all pairs of -if rule
-          // and live types.
-          for (DexType type : liveTypes) {
-            DexClass clazz = appView.appInfo().definitionFor(type);
-            if (clazz == null) {
-              continue;
-            }
-
-            // Check if the class matches the if-rule.
-            evaluator.evaluateIfRule(ifRule, clazz, clazz);
-
-            // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
-            if (options.enableVerticalClassMerging && appView.verticallyMergedClasses() != null) {
-              for (DexType sourceType : appView.verticallyMergedClasses().getSourcesFor(type)) {
-                // Note that, although `sourceType` has been merged into `type`, the dex class for
-                // `sourceType` is still available until the second round of tree shaking. This way
-                // we can still retrieve the access flags of `sourceType`.
-                DexClass sourceClass = appView.appInfo().definitionFor(sourceType);
-                assert sourceClass != null;
-                evaluator.evaluateIfRule(ifRule, sourceClass, clazz);
-              }
-            }
-          }
-        }
-        ThreadUtils.awaitFutures(evaluator.futures);
-      }
-    } finally {
-      application.timing.end();
-    }
-    return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+      Set<DexEncodedField> liveFields,
+      ExecutorService executorService) {
+    return new IfRuleEvaluator(liveMethods, liveFields, executorService);
   }
 
-  private class IfRuleEvaluator {
+  class IfRuleEvaluator {
 
-    private final Set<DexType> liveTypes;
     private final Set<DexEncodedMethod> liveMethods;
     private final Set<DexEncodedField> liveFields;
     private final ExecutorService executorService;
@@ -314,16 +280,56 @@
     private final List<Future<?>> futures = new ArrayList<>();
 
     public IfRuleEvaluator(
-        Set<DexType> liveTypes,
         Set<DexEncodedMethod> liveMethods,
         Set<DexEncodedField> liveFields,
         ExecutorService executorService) {
-      this.liveTypes = liveTypes;
       this.liveMethods = liveMethods;
       this.liveFields = liveFields;
       this.executorService = executorService;
     }
 
+    public ConsequentRootSet run(Set<DexType> liveTypes) throws ExecutionException {
+      application.timing.begin("Find consequent items for -if rules...");
+      try {
+        if (rules != null) {
+          for (ProguardConfigurationRule rule : rules) {
+            assert rule instanceof ProguardIfRule;
+            ProguardIfRule ifRule = (ProguardIfRule) rule;
+            // Depending on which types that trigger the -if rule, the application of the subsequent
+            // -keep rule may vary (due to back references). So, we need to try all pairs of -if
+            // rule and live types.
+            for (DexType type : liveTypes) {
+              DexClass clazz = appView.appInfo().definitionFor(type);
+              if (clazz == null) {
+                continue;
+              }
+
+              // Check if the class matches the if-rule.
+              evaluateIfRule(ifRule, clazz, clazz);
+
+              // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
+              if (options.enableVerticalClassMerging && appView.verticallyMergedClasses() != null) {
+                for (DexType sourceType : appView.verticallyMergedClasses().getSourcesFor(type)) {
+                  // Note that, although `sourceType` has been merged into `type`, the dex class for
+                  // `sourceType` is still available until the second round of tree shaking. This
+                  // way
+                  // we can still retrieve the access flags of `sourceType`.
+                  DexClass sourceClass = appView.appInfo().definitionFor(sourceType);
+                  assert sourceClass != null;
+                  evaluateIfRule(ifRule, sourceClass, clazz);
+                }
+              }
+            }
+          }
+          ThreadUtils.awaitFutures(futures);
+        }
+      } finally {
+        application.timing.end();
+      }
+      return new ConsequentRootSet(
+          neverInline, noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+    }
+
     /**
      * Determines if `sourceClass` satisfies the given if-rule. If `sourceClass` has not been merged
      * into another class, then `targetClass` is the same as `sourceClass`. Otherwise, `targetClass`
@@ -406,6 +412,16 @@
 
     private void materializeIfRule(ProguardIfRule rule) {
       ProguardIfRule materializedRule = rule.materialize();
+
+      // If the condition of the -if rule has any members, then we need to keep these members to
+      // ensure that the subsequent rule will be applied again in the second round of tree
+      // shaking.
+      InlineRule neverInlineRuleForCondition = materializedRule.neverInlineRuleForCondition();
+      if (neverInlineRuleForCondition != null) {
+        runPerRule(executorService, futures, neverInlineRuleForCondition, materializedRule);
+      }
+
+      // Keep whatever is required by the -if rule.
       runPerRule(executorService, futures, materializedRule.subsequentRule, materializedRule);
     }
   }
@@ -852,20 +868,26 @@
     } else if (context instanceof ProguardCheckDiscardRule) {
       checkDiscarded.add(item);
     } else if (context instanceof InlineRule) {
-      switch (((InlineRule) context).getType()) {
-        case ALWAYS:
-          if (item.isDexEncodedMethod()) {
+      if (item.isDexEncodedMethod()) {
+        switch (((InlineRule) context).getType()) {
+          case ALWAYS:
             alwaysInline.add(item.asDexEncodedMethod().method);
-          }
-          break;
-        case FORCE:
-          if (item.isDexEncodedMethod()) {
+            break;
+          case FORCE:
             forceInline.add(item.asDexEncodedMethod().method);
-          }
-          break;
-        case NEVER:
-          if (item.isDexEncodedMethod()) {
+            break;
+          case NEVER:
             neverInline.add(item.asDexEncodedMethod().method);
+            break;
+          default:
+            throw new Unreachable();
+        }
+      }
+    } else if (context instanceof ClassInlineRule) {
+      switch (((ClassInlineRule) context).getType()) {
+        case NEVER:
+          if (item.isDexClass()) {
+            neverClassInline.add(item.asDexClass().type);
           }
           break;
         default:
@@ -901,6 +923,7 @@
     public final Set<DexMethod> alwaysInline;
     public final Set<DexMethod> forceInline;
     public final Set<DexMethod> neverInline;
+    public final Set<DexType> neverClassInline;
     public final Set<DexType> neverMerge;
     public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
     public final Map<DexDefinition, ProguardMemberRule> assumedValues;
@@ -918,6 +941,7 @@
         Set<DexMethod> alwaysInline,
         Set<DexMethod> forceInline,
         Set<DexMethod> neverInline,
+        Set<DexType> neverClassInline,
         Set<DexType> neverMerge,
         Map<DexDefinition, ProguardMemberRule> noSideEffects,
         Map<DexDefinition, ProguardMemberRule> assumedValues,
@@ -932,7 +956,8 @@
       this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
       this.forceInline = Collections.unmodifiableSet(forceInline);
-      this.neverInline = Collections.unmodifiableSet(neverInline);
+      this.neverInline = neverInline;
+      this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
       this.neverMerge = Collections.unmodifiableSet(neverMerge);
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
@@ -984,16 +1009,19 @@
 
   // A partial RootSet that becomes live due to the enabled -if rule.
   static class ConsequentRootSet {
+    final Set<DexMethod> neverInline;
     final Map<DexDefinition, ProguardKeepRule> noShrinking;
     final Set<DexDefinition> noOptimization;
     final Set<DexDefinition> noObfuscation;
     final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
 
     private ConsequentRootSet(
+        Set<DexMethod> neverInline,
         Map<DexDefinition, ProguardKeepRule> noShrinking,
         Set<DexDefinition> noOptimization,
         Set<DexDefinition> noObfuscation,
         Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
+      this.neverInline = Collections.unmodifiableSet(neverInline);
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
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 b9b9a99..4dfee8e 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -60,6 +60,7 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -613,9 +614,28 @@
     assert result.assertDefinitionNotModified(appInfo.noSideEffects.keySet());
     // TODO(christofferqa): Enable this assert.
     // assert result.assertNotModified(appInfo.pinnedItems);
+    assert verifyGraphLense(graphLense);
     return result;
   }
 
+  private boolean verifyGraphLense(GraphLense graphLense) {
+    for (DexProgramClass clazz : appInfo.classes()) {
+      for (DexEncodedMethod encodedMethod : clazz.methods()) {
+        DexMethod method = encodedMethod.method;
+        DexMethod originalMethod = graphLense.getOriginalMethodSignature(method);
+
+        // Must be able to map back.
+        assert method == graphLense.getRenamedMethodSignature(originalMethod);
+
+        // Verify that all types are up-to-date. After vertical class merging, there should be no
+        // more references to types that have been merged into another type.
+        assert !mergedClasses.containsKey(method.proto.returnType);
+        assert Arrays.stream(method.proto.parameters.values).noneMatch(mergedClasses::containsKey);
+      }
+    }
+    return true;
+  }
+
   private GraphLense mergeClasses(GraphLense graphLense) {
     // Visit the program classes in a top-down order according to the class hierarchy.
     TopDownClassHierarchyTraversal.visit(appView, mergeCandidates, this::mergeClassIfPossible);
@@ -900,6 +920,7 @@
           // it turns out that the method is never used, it will be removed by the final round
           // of tree shaking.
           shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod);
+          deferredRenamings.recordCreationOfBridgeMethod(virtualMethod.method, shadowedBy.method);
           add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
         }
 
@@ -1109,6 +1130,7 @@
       SynthesizedBridgeCode code =
           new SynthesizedBridgeCode(
               newMethod,
+              graphLense.getOriginalMethodSignature(method.method),
               invocationTarget.method,
               invocationTarget.isPrivateMethod() ? DIRECT : STATIC);
 
@@ -1393,7 +1415,7 @@
         DexMethod newMethod = application.dexItemFactory.createMethod(newHolder, newProto,
             method.name);
         if (newMethod != encodedMethod.method) {
-          lense.map(encodedMethod.method, newMethod);
+          lense.move(encodedMethod.method, newMethod);
           methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
         }
       }
@@ -1411,7 +1433,7 @@
         DexType newHolder = fixupType(field.clazz);
         DexField newField = application.dexItemFactory.createField(newHolder, newType, field.name);
         if (newField != encodedField.field) {
-          lense.map(encodedField.field, newField);
+          lense.move(encodedField.field, newField);
           fields[i] = encodedField.toTypeSubstitutedField(newField);
         }
       }
@@ -1801,11 +1823,14 @@
   protected static class SynthesizedBridgeCode extends AbstractSynthesizedCode {
 
     private DexMethod method;
+    private DexMethod originalMethod;
     private DexMethod invocationTarget;
     private Type type;
 
-    public SynthesizedBridgeCode(DexMethod method, DexMethod invocationTarget, Type type) {
+    public SynthesizedBridgeCode(
+        DexMethod method, DexMethod originalMethod, DexMethod invocationTarget, Type type) {
       this.method = method;
+      this.originalMethod = originalMethod;
       this.invocationTarget = invocationTarget;
       this.type = type;
     }
@@ -1832,6 +1857,7 @@
           new ForwardMethodSourceCode(
               method.holder,
               method,
+              originalMethod,
               type == DIRECT ? method.holder : null,
               invocationTarget,
               type,
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 43c5e92..6b66465 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -17,10 +17,10 @@
 import com.android.tools.r8.shaking.VerticalClassMerger.SynthesizedBridgeCode;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -53,9 +53,10 @@
 public class VerticalClassMergerGraphLense extends NestedGraphLense {
   private final AppInfo appInfo;
 
-  private final Set<DexMethod> mergedMethods;
   private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
       contextualVirtualToDirectMethodMaps;
+  private final Set<DexMethod> mergedMethods;
+  private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
   public VerticalClassMergerGraphLense(
       AppInfo appInfo,
@@ -65,6 +66,7 @@
       Map<DexType, Map<DexMethod, GraphLenseLookupResult>> contextualVirtualToDirectMethodMaps,
       BiMap<DexField, DexField> originalFieldSignatures,
       BiMap<DexMethod, DexMethod> originalMethodSignatures,
+      Map<DexMethod, DexMethod> originalMethodSignaturesForBridges,
       GraphLense previousLense) {
     super(
         ImmutableMap.of(),
@@ -75,8 +77,15 @@
         previousLense,
         appInfo.dexItemFactory);
     this.appInfo = appInfo;
-    this.mergedMethods = mergedMethods;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
+    this.mergedMethods = mergedMethods;
+    this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    return super.getOriginalMethodSignature(
+        originalMethodSignaturesForBridges.getOrDefault(method, method));
   }
 
   @Override
@@ -154,7 +163,9 @@
     private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
         contextualVirtualToDirectMethodMaps = new HashMap<>();
 
-    private final Map<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
+        new IdentityHashMap<>();
 
     public GraphLense build(
         GraphLense previousLense,
@@ -184,10 +195,9 @@
           getMergedMethodSignaturesAfterClassMerging(
               mergedMethodsBuilder.build(), mergedClasses, appInfo.dexItemFactory, cache),
           contextualVirtualToDirectMethodMaps,
-          getOriginalFieldSignaturesAfterClassMerging(
-              originalFieldSignatures, mergedClasses, appInfo.dexItemFactory),
-          getOriginalMethodSignaturesAfterClassMerging(
-              originalMethodSignatures, mergedClasses, appInfo.dexItemFactory, cache),
+          originalFieldSignatures,
+          originalMethodSignatures,
+          originalMethodSignaturesForBridges,
           previousLense);
     }
 
@@ -207,44 +217,6 @@
       return result.build();
     }
 
-    private static BiMap<DexField, DexField> getOriginalFieldSignaturesAfterClassMerging(
-        Map<DexField, DexField> originalFieldSignatures,
-        Map<DexType, DexType> mergedClasses,
-        DexItemFactory dexItemFactory) {
-      ImmutableBiMap.Builder<DexField, DexField> result = ImmutableBiMap.builder();
-      for (Map.Entry<DexField, DexField> entry : originalFieldSignatures.entrySet()) {
-        result.put(
-            getFieldSignatureAfterClassMerging(entry.getKey(), mergedClasses, dexItemFactory),
-            entry.getValue());
-      }
-      return result.build();
-    }
-
-    private static BiMap<DexMethod, DexMethod> getOriginalMethodSignaturesAfterClassMerging(
-        Map<DexMethod, DexMethod> originalMethodSignatures,
-        Map<DexType, DexType> mergedClasses,
-        DexItemFactory dexItemFactory,
-        Map<DexProto, DexProto> cache) {
-      ImmutableBiMap.Builder<DexMethod, DexMethod> result = ImmutableBiMap.builder();
-      for (Map.Entry<DexMethod, DexMethod> entry : originalMethodSignatures.entrySet()) {
-        result.put(
-            getMethodSignatureAfterClassMerging(
-                entry.getKey(), mergedClasses, dexItemFactory, cache),
-            entry.getValue());
-      }
-      return result.build();
-    }
-
-    private static DexField getFieldSignatureAfterClassMerging(
-        DexField signature, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
-      DexType newClass = mergedClasses.getOrDefault(signature.clazz, signature.clazz);
-      DexType newType = mergedClasses.getOrDefault(signature.type, signature.type);
-      if (signature.clazz.equals(newClass) && signature.type.equals(newType)) {
-        return signature;
-      }
-      return dexItemFactory.createField(newClass, newType, signature.name);
-    }
-
     private static DexMethod getMethodSignatureAfterClassMerging(
         DexMethod signature,
         Map<DexType, DexType> mergedClasses,
@@ -285,6 +257,10 @@
       originalMethodSignatures.put(to, from);
     }
 
+    public void recordCreationOfBridgeMethod(DexMethod from, DexMethod to) {
+      originalMethodSignaturesForBridges.put(to, from);
+    }
+
     public void mapVirtualMethodToDirectInType(
         DexMethod from, GraphLenseLookupResult to, DexType type) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
@@ -297,6 +273,7 @@
       methodMap.putAll(builder.methodMap);
       mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
       originalMethodSignatures.putAll(builder.originalMethodSignatures);
+      originalMethodSignaturesForBridges.putAll(builder.originalMethodSignaturesForBridges);
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
         Map<DexMethod, GraphLenseLookupResult> current =
             contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index ed12177..49573db 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.FileUtils.isClassFile;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
 
-import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.DataDirectoryResource;
@@ -56,10 +55,19 @@
   private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
   private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
 
+  // List of internally added archive providers for which we must close their resources.
+  private final ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose;
+
   private final StringResource proguardMapOutputData;
   private final List<StringResource> mainDexListResources;
   private final List<String> mainDexClasses;
 
+  public void closeInternalArchiveProviders() throws IOException {
+    for (InternalArchiveClassFileProvider provider : archiveProvidersToClose) {
+      provider.close();
+    }
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
@@ -118,6 +126,7 @@
       ImmutableMap<Resource, String> programResourcesMainDescriptor,
       ImmutableList<ClassFileResourceProvider> classpathResourceProviders,
       ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
+      ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose,
       StringResource proguardMapOutputData,
       List<StringResource> mainDexListResources,
       List<String> mainDexClasses) {
@@ -125,9 +134,20 @@
     this.programResourcesMainDescriptor = programResourcesMainDescriptor;
     this.classpathResourceProviders = classpathResourceProviders;
     this.libraryResourceProviders = libraryResourceProviders;
+    this.archiveProvidersToClose = archiveProvidersToClose;
     this.proguardMapOutputData = proguardMapOutputData;
     this.mainDexListResources = mainDexListResources;
     this.mainDexClasses = mainDexClasses;
+    assert verifyInternalProvidersInCloseSet(classpathResourceProviders, archiveProvidersToClose);
+    assert verifyInternalProvidersInCloseSet(libraryResourceProviders, archiveProvidersToClose);
+  }
+
+  private static boolean verifyInternalProvidersInCloseSet(
+      ImmutableList<ClassFileResourceProvider> providers,
+      ImmutableList<InternalArchiveClassFileProvider> providersToClose) {
+    return providers.stream()
+        .allMatch(
+            p -> !(p instanceof InternalArchiveClassFileProvider) || providersToClose.contains(p));
   }
 
   static Reporter defaultReporter() {
@@ -327,6 +347,8 @@
     private final Map<ProgramResource, String> programResourcesMainDescriptor = new HashMap<>();
     private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
     private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
+    private final List<InternalArchiveClassFileProvider> archiveProvidersToClose =
+        new ArrayList<>();
     private List<StringResource> mainDexListResources = new ArrayList<>();
     private List<String> mainDexListClasses = new ArrayList<>();
     private boolean ignoreDexInArchive = false;
@@ -347,6 +369,7 @@
       programResourceProviders.addAll(app.programResourceProviders);
       classpathResourceProviders.addAll(app.classpathResourceProviders);
       libraryResourceProviders.addAll(app.libraryResourceProviders);
+      archiveProvidersToClose.addAll(app.archiveProvidersToClose);
       mainDexListResources = app.mainDexListResources;
       mainDexListClasses = app.mainDexClasses;
     }
@@ -436,7 +459,9 @@
       for (FilteredClassPath archive : filteredArchives) {
         assert isArchive(archive.getPath());
         try {
-          libraryResourceProviders.add(new FilteredArchiveClassFileProvider(archive));
+          FilteredArchiveClassFileProvider provider = new FilteredArchiveClassFileProvider(archive);
+          archiveProvidersToClose.add(provider);
+          libraryResourceProviders.add(provider);
         } catch (IOException e) {
           reporter.error(new ExceptionDiagnostic(e, new PathOrigin(archive.getPath())));
         }
@@ -625,6 +650,7 @@
           ImmutableMap.copyOf(programResourcesMainDescriptor),
           ImmutableList.copyOf(classpathResourceProviders),
           ImmutableList.copyOf(libraryResourceProviders),
+          ImmutableList.copyOf(archiveProvidersToClose),
           proguardMapOutputData,
           mainDexListResources,
           mainDexListClasses);
@@ -673,7 +699,9 @@
       }
       if (isArchive(file)) {
         try {
-          providerList.add(new ArchiveClassFileProvider(file));
+          InternalArchiveClassFileProvider provider = new InternalArchiveClassFileProvider(file);
+          archiveProvidersToClose.add(provider);
+          providerList.add(provider);
         } catch (IOException e) {
           reporter.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
         }
diff --git a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
index 0c6cc14..43cbb4d 100644
--- a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import java.io.IOException;
 
 // Internal filtered class-file provider.
-class FilteredArchiveClassFileProvider extends ArchiveClassFileProvider {
+class FilteredArchiveClassFileProvider extends InternalArchiveClassFileProvider {
 
   FilteredArchiveClassFileProvider(FilteredClassPath archive) throws IOException {
     super(archive.getPath(), entry -> archive.matchesFile(entry));
diff --git a/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
new file mode 100644
index 0000000..cf30d7a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
@@ -0,0 +1,125 @@
+// 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.utils;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Internal-only provider for providing class files when clients use the addClasspathFiles and
+ * addLibraryFiles API.
+ *
+ * <p>The purpose of the internal provider is that for the API use above we know it is safe to use
+ * the zip-file descriptor throughout compilation and close at the end of reading. It must also be
+ * safe to reopen it as currently our own tests reuse AndroidApp structures.
+ */
+class InternalArchiveClassFileProvider implements ClassFileResourceProvider, AutoCloseable {
+  private final Path path;
+  private final Origin origin;
+  private final Set<String> descriptors = new HashSet<>();
+
+  private ZipFile openedZipFile = null;
+
+  /**
+   * Creates a lazy class-file program-resource provider.
+   *
+   * @param archive Zip archive to provide resources from.
+   */
+  public InternalArchiveClassFileProvider(Path archive) throws IOException {
+    this(archive, entry -> true);
+  }
+
+  /**
+   * Creates a lazy class-file program-resource provider with an include filter.
+   *
+   * @param archive Zip archive to provide resources from.
+   * @param include Predicate deciding if a given class-file entry should be provided.
+   */
+  public InternalArchiveClassFileProvider(Path archive, Predicate<String> include)
+      throws IOException {
+    assert isArchive(archive);
+    path = archive;
+    origin = new PathOrigin(archive);
+    final Enumeration<? extends ZipEntry> entries = getOpenZipFile().entries();
+    while (entries.hasMoreElements()) {
+      ZipEntry entry = entries.nextElement();
+      String name = entry.getName();
+      if (ZipUtils.isClassFile(name) && include.test(name)) {
+        descriptors.add(DescriptorUtils.guessTypeDescriptor(name));
+      }
+    }
+  }
+
+  @Override
+  public Set<String> getClassDescriptors() {
+    return Collections.unmodifiableSet(descriptors);
+  }
+
+  @Override
+  public ProgramResource getProgramResource(String descriptor) {
+    if (!descriptors.contains(descriptor)) {
+      return null;
+    }
+    try {
+      ZipEntry zipEntry = getZipEntryFromDescriptor(descriptor);
+      try (InputStream inputStream = getOpenZipFile().getInputStream(zipEntry)) {
+        return ProgramResource.fromBytes(
+            new ArchiveEntryOrigin(zipEntry.getName(), origin),
+            Kind.CF,
+            ByteStreams.toByteArray(inputStream),
+            Collections.singleton(descriptor));
+      }
+    } catch (IOException e) {
+      throw new CompilationError("Failed to read '" + descriptor, origin);
+    }
+  }
+
+  private ZipFile getOpenZipFile() throws IOException {
+    if (openedZipFile == null) {
+      try {
+        openedZipFile = new ZipFile(path.toFile(), StandardCharsets.UTF_8);
+      } catch (IOException e) {
+        if (!Files.exists(path)) {
+          throw new NoSuchFileException(path.toString());
+        } else {
+          throw e;
+        }
+      }
+    }
+    return openedZipFile;
+  }
+
+  @Override
+  public void close() throws IOException {
+    openedZipFile.close();
+    openedZipFile = null;
+  }
+
+  private ZipEntry getZipEntryFromDescriptor(String descriptor) throws IOException {
+    return getOpenZipFile()
+        .getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
+  }
+}
diff --git a/src/test/examples/inlining/AlwaysInline.java b/src/test/examples/inlining/AlwaysInline.java
index 815d50a..22cb31e 100644
--- a/src/test/examples/inlining/AlwaysInline.java
+++ b/src/test/examples/inlining/AlwaysInline.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package inlining;
 
-public @interface AlwaysInline {
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
 
-}
+@Target({ElementType.METHOD})
+public @interface AlwaysInline {}
diff --git a/src/test/examples/inlining/NeverInline.java b/src/test/examples/inlining/NeverInline.java
index 0db5239..0c5b581 100644
--- a/src/test/examples/inlining/NeverInline.java
+++ b/src/test/examples/inlining/NeverInline.java
@@ -3,4 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package inlining;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
 public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/NeverClassInline.java b/src/test/java/com/android/tools/r8/NeverClassInline.java
new file mode 100644
index 0000000..aa7f9b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NeverClassInline.java
@@ -0,0 +1,10 @@
+// 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NeverClassInline {}
diff --git a/src/test/java/com/android/tools/r8/NeverMerge.java b/src/test/java/com/android/tools/r8/NeverMerge.java
index 7fc97b9..7c6922a 100644
--- a/src/test/java/com/android/tools/r8/NeverMerge.java
+++ b/src/test/java/com/android/tools/r8/NeverMerge.java
@@ -3,4 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
 public @interface NeverMerge {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 3a249e6..5550415 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -30,6 +30,7 @@
   }
 
   private boolean enableInliningAnnotations = false;
+  private boolean enableClassInliningAnnotations = false;
   private boolean enableMergeAnnotations = false;
 
   @Override
@@ -41,7 +42,7 @@
   R8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
-    if (enableInliningAnnotations || enableMergeAnnotations) {
+    if (enableInliningAnnotations || enableClassInliningAnnotations || enableMergeAnnotations) {
       ToolHelper.allowTestProguardOptions(builder);
     }
     StringBuilder proguardMapBuilder = new StringBuilder();
@@ -71,6 +72,14 @@
     return self();
   }
 
+  public R8TestBuilder enableClassInliningAnnotations() {
+    if (!enableClassInliningAnnotations) {
+      enableClassInliningAnnotations = true;
+      addKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
+    }
+    return self();
+  }
+
   public R8TestBuilder enableMergeAnnotations() {
     if (!enableMergeAnnotations) {
       enableMergeAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d6e7c0d..f968b8c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -143,6 +143,10 @@
         this.shortName = shortName;
       }
 
+      public boolean isLatest() {
+        return this == DEFAULT;
+      }
+
       public boolean isNewerThan(Version other) {
         return compareTo(other) > 0;
       }
diff --git a/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
new file mode 100644
index 0000000..010f903
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
@@ -0,0 +1,80 @@
+// 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.d8;
+
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.hamcrest.Matcher;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class IncompatiblePrimitiveTypesTest extends TestBase {
+
+  @ClassRule
+  public static TemporaryFolder tempFolder = ToolHelper.getTemporaryFolderForTest();
+
+  private static Path inputJar;
+  private static String expectedOutput = "true";
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    ClassBuilder classBuilder = jasminBuilder.addClass("TestClass");
+    classBuilder
+        .staticMethodBuilder("main", ImmutableList.of("[Ljava/lang/String;"), "V")
+        .setCode(
+            "invokestatic TestClass/getByte()B", "invokestatic TestClass/takeBoolean(Z)V", "return")
+        .build();
+    classBuilder
+        .staticMethodBuilder("getByte", ImmutableList.of(), "B")
+        .setCode("iconst_1", "ireturn")
+        .build();
+    classBuilder
+        .staticMethodBuilder("takeBoolean", ImmutableList.of("Z"), "V")
+        .setCode(
+            "getstatic java/lang/System/out Ljava/io/PrintStream;",
+            "iload_0",
+            "invokevirtual java/io/PrintStream/print(Z)V",
+            "return")
+        .build();
+    inputJar = tempFolder.getRoot().toPath().resolve("input.jar");
+    jasminBuilder.writeJar(inputJar);
+  }
+
+  @Test
+  public void jvmTest() throws Exception {
+    assumeTrue(
+        "JVM test independent of Art version - only run when testing on latest",
+        ToolHelper.getDexVm().getVersion().isLatest());
+    testForJvm().addClasspath(inputJar).run("TestClass").assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void dexTest() throws Exception {
+    TestRunResult d8Result = testForD8().addProgramFiles(inputJar).run("TestClass");
+    TestRunResult dxResult = testForDX().addProgramFiles(inputJar).run("TestClass");
+    if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)) {
+      d8Result.assertSuccessWithOutput(expectedOutput);
+      dxResult.assertSuccessWithOutput(expectedOutput);
+    } else {
+      // TODO(b/119812046): On Art 4.0.4 and 4.4.4 it is a verification error to use one short type
+      // as another short type.
+      Matcher<String> expectedError = containsString("java.lang.VerifyError");
+      d8Result.assertFailureWithErrorThatMatches(expectedError);
+      dxResult.assertFailureWithErrorThatMatches(expectedError);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
new file mode 100644
index 0000000..d9dfcd8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -0,0 +1,136 @@
+// 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.optimize.uninstantiatedtypes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+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 ParameterRewritingTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public ParameterRewritingTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Ignore("b/110806787")
+  @Test
+  public void test() throws Exception {
+    String expected =
+        StringUtils.lines(
+            "Factory.createStatic() -> null",
+            "Factory.createStaticWithUnused1() -> null",
+            "Factory.createStaticWithUnused2() -> null",
+            "Factory.createStaticWithUnused3() -> null");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(ParameterRewritingTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .addKeepRules("-dontobfuscate")
+            .addOptionsModification(options -> options.enableClassInlining = false)
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expected)
+            .inspector();
+
+    ClassSubject factoryClassSubject = inspector.clazz(Factory.class);
+    MethodSubject createStaticMethodSubject =
+        factoryClassSubject.uniqueMethodWithName("createStatic");
+    assertThat(createStaticMethodSubject, isPresent());
+    assertEquals(1, createStaticMethodSubject.getMethod().method.proto.parameters.size());
+
+    for (int i = 1; i <= 3; ++i) {
+      String createStaticWithUnusedMethodName = "createStaticWithUnused" + i;
+      MethodSubject createStaticWithUnusedMethodSubject =
+          factoryClassSubject.uniqueMethodWithName(createStaticWithUnusedMethodName);
+      assertThat(createStaticWithUnusedMethodSubject, isPresent());
+
+      DexMethod method = createStaticWithUnusedMethodSubject.getMethod().method;
+      assertEquals(1, method.proto.parameters.size());
+      assertEquals("java.lang.String", method.proto.parameters.toString());
+    }
+
+    assertThat(inspector.clazz(Uninstantiated.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Object obj1 = Factory.createStatic(null, "Factory.createStatic()");
+      System.out.println(" -> " + obj1);
+
+      Object obj2 =
+          Factory.createStaticWithUnused1(new Object(), null, "Factory.createStaticWithUnused1()");
+      System.out.println(" -> " + obj2);
+
+      Object obj3 =
+          Factory.createStaticWithUnused2(null, new Object(), "Factory.createStaticWithUnused2()");
+      System.out.println(" -> " + obj3);
+
+      Object obj4 =
+          Factory.createStaticWithUnused3(null, "Factory.createStaticWithUnused3()", new Object());
+      System.out.println(" -> " + obj4);
+    }
+  }
+
+  @NeverMerge
+  static class Uninstantiated {}
+
+  @NeverMerge
+  static class Factory {
+
+    @NeverInline
+    public static Object createStatic(Uninstantiated uninstantiated, String msg) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+
+    @NeverInline
+    public static Object createStaticWithUnused1(
+        Object unused, Uninstantiated uninstantiated, String msg) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+
+    @NeverInline
+    public static Object createStaticWithUnused2(
+        Uninstantiated uninstantiated, Object unused, String msg) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+
+    @NeverInline
+    public static Object createStaticWithUnused3(
+        Uninstantiated uninstantiated, String msg, Object unused) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
new file mode 100644
index 0000000..0fb68e6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -0,0 +1,149 @@
+// 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.optimize.uninstantiatedtypes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+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 VoidReturnTypeRewritingTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public VoidReturnTypeRewritingTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Ignore("b/110806787")
+  @Test
+  public void test() throws Exception {
+    String expected =
+        StringUtils.lines(
+            "Factory.createStatic() -> null",
+            "Factory.createVirtual() -> null",
+            "SubFactory.createVirtual() -> null",
+            "SubSubFactory.createVirtual() -> null");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(VoidReturnTypeRewritingTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .addKeepRules("-dontobfuscate")
+            .addOptionsModification(options -> options.enableClassInlining = false)
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expected)
+            .inspector();
+
+    ClassSubject factoryClassSubject = inspector.clazz(Factory.class);
+    MethodSubject createStaticMethodSubject =
+        factoryClassSubject.uniqueMethodWithName("createStatic");
+    assertThat(createStaticMethodSubject, isPresent());
+    assertTrue(createStaticMethodSubject.getMethod().method.proto.returnType.isVoidType());
+    MethodSubject createVirtualMethodSubject =
+        factoryClassSubject.uniqueMethodWithName("createVirtual");
+    assertThat(createVirtualMethodSubject, isPresent());
+    assertTrue(createVirtualMethodSubject.getMethod().method.proto.returnType.isVoidType());
+
+    createVirtualMethodSubject =
+        inspector.clazz(SubFactory.class).uniqueMethodWithName("createVirtual");
+    assertThat(createVirtualMethodSubject, isPresent());
+    assertTrue(createVirtualMethodSubject.getMethod().method.proto.returnType.isVoidType());
+
+    ClassSubject subSubFactoryClassSubject = inspector.clazz(SubSubFactory.class);
+    assertThat(subSubFactoryClassSubject.method("void", "createVirtual"), isPresent());
+    assertThat(
+        subSubFactoryClassSubject.method(SubUninstantiated.class.getTypeName(), "createVirtual"),
+        isPresent());
+
+    // TODO(b/110806787): Uninstantiated is kept because SubUninstantiated inherits from it.
+    // We should consider rewriting SubUninstantiated such that it no longer inherits from
+    // Uninstantiated.
+    assertThat(inspector.clazz(Uninstantiated.class), isPresent());
+    assertThat(inspector.clazz(SubUninstantiated.class), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Uninstantiated obj1 = Factory.createStatic();
+      System.out.println(" -> " + obj1);
+
+      Uninstantiated obj2 = new Factory().createVirtual();
+      System.out.println(" -> " + obj2);
+
+      Uninstantiated obj3 = new SubFactory().createVirtual();
+      System.out.println(" -> " + obj3);
+
+      Uninstantiated obj4 = new SubSubFactory().createVirtual();
+      System.out.println(" -> " + obj4);
+    }
+  }
+
+  @NeverMerge
+  static class Uninstantiated {}
+
+  @NeverMerge
+  static class SubUninstantiated extends Uninstantiated {}
+
+  @NeverMerge
+  static class Factory {
+
+    @NeverInline
+    public static Uninstantiated createStatic() {
+      System.out.print("Factory.createStatic()");
+      return null;
+    }
+
+    @NeverInline
+    public Uninstantiated createVirtual() {
+      System.out.print("Factory.createVirtual()");
+      return null;
+    }
+  }
+
+  @NeverMerge
+  static class SubFactory extends Factory {
+
+    @Override
+    @NeverInline
+    public Uninstantiated createVirtual() {
+      System.out.print("SubFactory.createVirtual()");
+      return null;
+    }
+  }
+
+  @NeverMerge
+  static class SubSubFactory extends SubFactory {
+
+    @Override
+    @NeverInline
+    public SubUninstantiated createVirtual() {
+      System.out.print("SubSubFactory.createVirtual()");
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 4a7db37..0f3accb 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -168,7 +168,8 @@
         getProguardConfig(enableAdaptResourceFileContents, adaptResourceFileContentsPathFilter),
         "-neverinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B {",
         "  public void method();",
-        "}");
+        "}",
+        "-neverclassinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B");
   }
 
   @Test
@@ -324,13 +325,7 @@
         .addDataResources(getDataResources())
         .enableProguardTestOptions()
         .addKeepRules(proguardConfig)
-        .addOptionsModification(
-            o -> {
-              // TODO(christofferqa): Class inliner should respect -neverinline.
-              o.enableClassInlining = false;
-              o.enableVerticalClassMerging = true;
-              o.dataResourceConsumer = dataResourceConsumer;
-            })
+        .addOptionsModification(o -> o.dataResourceConsumer = dataResourceConsumer)
         .compile();
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index ecfddf4..095d7e9 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.io.File;
@@ -93,15 +94,15 @@
 
   private static String getProguardConfigWithNeverInline(
       boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
-    return String.join(
-        System.lineSeparator(),
+    return StringUtils.lines(
         getProguardConfig(enableAdaptResourceFileNames, adaptResourceFileNamesPathFilter),
         "-neverinline class " + adaptresourcefilenames.A.class.getName() + " {",
         "  public void method();",
         "}",
         "-neverinline class " + adaptresourcefilenames.B.Inner.class.getName() + " {",
         "  public void method();",
-        "}");
+        "}",
+        "-neverclassinline class *");
   }
 
   @Test
@@ -262,9 +263,6 @@
     return ToolHelper.runR8(
         command,
         options -> {
-          // TODO(christofferqa): Class inliner should respect -neverinline.
-          options.enableClassInlining = false;
-          options.enableVerticalClassMerging = true;
           options.dataResourceConsumer = dataResourceConsumer;
           options.proguardMapConsumer = proguardMapConsumer;
           options.testing.suppressExperimentalCfBackendWarning = true;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index e420e1e..a603f92 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -11,13 +11,11 @@
 
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
index d02fa5b..de981ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -48,18 +48,17 @@
 
 @RunWith(Parameterized.class)
 public class IfRuleWithInlining extends ProguardCompatibilityTestBase {
-  private final static List<Class> CLASSES = ImmutableList.of(
-      A.class, D.class, Main.class);
+  private static final List<Class> CLASSES = ImmutableList.of(A.class, D.class, Main.class);
 
   private final Shrinker shrinker;
-  private final boolean inlineMethod;
+  private final boolean neverInlineMethod;
 
-  public IfRuleWithInlining(Shrinker shrinker, boolean inlineMethod) {
+  public IfRuleWithInlining(Shrinker shrinker, boolean neverInlineMethod) {
     this.shrinker = shrinker;
-    this.inlineMethod = inlineMethod;
+    this.neverInlineMethod = neverInlineMethod;
   }
 
-  @Parameters(name = "shrinker: {0} inlineMethod: {1}")
+  @Parameters(name = "shrinker: {0} neverInlineMethod: {1}")
   public static Collection<Object[]> data() {
     // We don't run this on Proguard, as triggering inlining in Proguard is out of our control.
     return ImmutableList.of(
@@ -73,10 +72,9 @@
     CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazzA = inspector.clazz(A.class);
     assertThat(clazzA, isPresent());
-    // A.a might be inlined.
-    assertEquals(!inlineMethod, clazzA.method("int", "a", ImmutableList.of()).isPresent());
-    // TODO(110148109): class D should be present - inlining or not.
-    assertEquals(!inlineMethod, inspector.clazz(D.class).isPresent());
+    // A.a should not be inlined.
+    assertThat(clazzA.method("int", "a", ImmutableList.of()), isPresent());
+    assertThat(inspector.clazz(D.class), isPresent());
     ProcessResult result;
     if (shrinker == Shrinker.R8) {
       result = runOnArtRaw(app, Main.class.getName());
@@ -87,22 +85,18 @@
       result = ToolHelper.runJava(file, Main.class.getName());
     }
     assertEquals(0, result.exitCode);
-    // TODO(110148109): Output should be the same - inlining or not.
-    assertEquals(!inlineMethod ? "1" : "2", result.stdout);
+    assertEquals("1", result.stdout);
   }
 
   @Test
   public void testMergedClassMethodInIfRule() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-keep class **.Main { public static void main(java.lang.String[]); }",
-        inlineMethod
-            ? "-forceinline class **.A { int a(); }"
-            : "-neverinline class **.A { int a(); }",
-        "-if class **.A { static int a(); }",
-        "-keep class **.D",
-        "-dontobfuscate"
-    );
-
+    List<String> config =
+        ImmutableList.of(
+            "-keep class **.Main { public static void main(java.lang.String[]); }",
+            neverInlineMethod ? "-neverinline class **.A { int a(); }" : "",
+            "-if class **.A { static int a(); }",
+            "-keep class **.D",
+            "-dontobfuscate");
     check(runShrinker(shrinker, CLASSES, config));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index 57ad556..f470aab 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -8,14 +8,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
@@ -23,6 +22,7 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+@NeverClassInline
 class A {
   int x = 1;
   int a() throws ClassNotFoundException {
@@ -34,6 +34,7 @@
   }
 }
 
+@NeverClassInline
 class B extends A {
   int y = 2;
   int b() {
@@ -41,6 +42,7 @@
   }
 }
 
+@NeverClassInline
 class C extends B {
   int z = 3;
   int c() {
@@ -48,8 +50,8 @@
   }
 }
 
-class D {
-}
+@NeverClassInline
+class D {}
 
 class Main {
   public static void main(String[] args) throws ClassNotFoundException {
@@ -61,7 +63,7 @@
 @RunWith(Parameterized.class)
 public class IfRuleWithVerticalClassMerging extends TestBase {
 
-  private static final List<Class> CLASSES =
+  private static final List<Class<?>> CLASSES =
       ImmutableList.of(A.class, B.class, C.class, D.class, Main.class);
 
   private final Backend backend;
@@ -84,35 +86,15 @@
 
   private void configure(InternalOptions options) {
     options.enableVerticalClassMerging = enableVerticalClassMerging;
-
-    // TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
-    options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-  }
-
-  private void check(AndroidApp app) throws Exception {
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazzA = inspector.clazz(A.class);
-    assertEquals(!enableVerticalClassMerging, clazzA.isPresent());
-    ClassSubject clazzB = inspector.clazz(B.class);
-    assertThat(clazzB, isPresent());
-    ClassSubject clazzD = inspector.clazz(D.class);
-    assertThat(clazzD, isPresent());
-    assertEquals("123456", runOnVM(app, Main.class, backend));
   }
 
   @Test
   public void testMergedClassInIfRule() throws Exception {
     // Class C is kept, meaning that it will not be touched.
     // Class A will be merged into class B.
-    String config =
-        String.join(
-            System.lineSeparator(),
-            "-keep class **.Main { public static void main(java.lang.String[]); }",
-            "-keep class **.C",
-            "-if class **.A",
-            "-keep class **.D",
-            "-dontobfuscate");
-    check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
+    runTestWithProguardConfig(
+        StringUtils.lines(
+            "-keep class **.C", "-if class **.A", "-keep class **.D", "-dontobfuscate"));
   }
 
   @Test
@@ -120,15 +102,9 @@
     // Class C is kept, meaning that it will not be touched.
     // Class A will be merged into class B.
     // Main.main access A.x, so that field exists satisfying the if rule.
-    String config =
-        String.join(
-            System.lineSeparator(),
-            "-keep class **.Main { public static void main(java.lang.String[]); }",
-            "-keep class **.C",
-            "-if class **.A { int x; }",
-            "-keep class **.D",
-            "-dontobfuscate");
-    check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
+    runTestWithProguardConfig(
+        StringUtils.lines(
+            "-keep class **.C", "-if class **.A { int x; }", "-keep class **.D", "-dontobfuscate"));
   }
 
   @Test
@@ -136,14 +112,31 @@
     // Class C is kept, meaning that it will not be touched.
     // Class A will be merged into class B.
     // Main.main access A.a(), that method exists satisfying the if rule.
-    String config =
-        String.join(
-            System.lineSeparator(),
-            "-keep class **.Main { public static void main(java.lang.String[]); }",
+    runTestWithProguardConfig(
+        StringUtils.lines(
             "-keep class **.C",
             "-if class **.A { int a(); }",
             "-keep class **.D",
-            "-dontobfuscate");
-    check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
+            "-dontobfuscate"));
+  }
+
+  private void runTestWithProguardConfig(String config) throws Exception {
+    CodeInspector inspector =
+        testForR8(backend)
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(Main.class)
+            .addKeepRules(config)
+            .enableClassInliningAnnotations()
+            .addOptionsModification(this::configure)
+            .run(Main.class)
+            .assertSuccessWithOutput("123456")
+            .inspector();
+
+    ClassSubject clazzA = inspector.clazz(A.class);
+    assertEquals(!enableVerticalClassMerging, clazzA.isPresent());
+    ClassSubject clazzB = inspector.clazz(B.class);
+    assertThat(clazzB, isPresent());
+    ClassSubject clazzD = inspector.clazz(D.class);
+    assertThat(clazzD, isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index 7c85a7b..879efe0 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -55,6 +56,7 @@
       }
     }
 
+    @NeverClassInline
     static class TestClass extends SuperTestClass {
 
       private A field = null;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
index cda8096..10a5ba0 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -5,18 +5,14 @@
 package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
@@ -42,7 +38,7 @@
   static class Unused {}
 
   final Backend backend;
-  final List<Class> classes;
+  final List<Class<?>> classes;
   final boolean enableVerticalClassMerging;
 
   public MergedTypeBaseTest(Backend backend, boolean enableVerticalClassMerging) {
@@ -54,7 +50,7 @@
     this.backend = backend;
     this.enableVerticalClassMerging = enableVerticalClassMerging;
     this.classes =
-        ImmutableList.<Class>builder()
+        ImmutableList.<Class<?>>builder()
             .add(A.class, B.class, C.class, I.class, J.class, K.class, Unused.class, getTestClass())
             .addAll(additionalClasses)
             .build();
@@ -95,17 +91,18 @@
     String expected = getExpectedStdout();
     assertEquals(expected, runOnJava(getTestClass()));
 
-    String config =
-        StringUtils.joinLines(
-            "-keep class " + getTestClass().getTypeName() + " {",
-            "  public static void main(java.lang.String[]);",
-            "}",
+    testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(getTestClass())
+        .addKeepRules(
             getConditionForProguardIfRule(),
             "-keep class " + Unused.class.getTypeName(),
-            getAdditionalKeepRules());
-    AndroidApp output = compileWithR8(readClasses(classes), config, this::configure, backend);
-    assertEquals(expected, runOnVM(output, getTestClass(), backend));
-    inspect(new CodeInspector(output));
+            getAdditionalKeepRules())
+        .addOptionsModification(this::configure)
+        .enableClassInliningAnnotations()
+        .run(getTestClass())
+        .assertSuccessWithOutput(expected)
+        .inspect(this::inspect);
   }
 
   private void configure(InternalOptions options) {
@@ -115,8 +112,5 @@
     // To ensure that the handling of extends and implements rules work as intended,
     // and that we don't end up keeping `Unused` only because one of the two implementations work.
     options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong = false;
-
-    // TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
-    options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
   }
 }