Merge "Do not warn about implements/extends rules that use wildcards"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7f77c6b..494a62b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -431,6 +431,7 @@
       new SourceFileRewriter(appView.appInfo(), options).run();
       timing.end();
 
+      MainDexClasses mainDexClasses = MainDexClasses.NONE;
       if (!options.mainDexKeepRules.isEmpty()) {
         appView.setAppInfo(new AppInfoWithSubtyping(application));
         Enqueuer enqueuer = new Enqueuer(appView, options, true);
@@ -444,8 +445,7 @@
         // LiveTypes is the tracing result.
         Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
         // Calculate the automatic main dex list according to legacy multidex constraints.
-        MainDexClasses mainDexClasses =
-            new MainDexListBuilder(mainDexBaseClasses, application).run();
+        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
         if (!mainDexRootSet.checkDiscarded.isEmpty()) {
           new DiscardedChecker(
                   mainDexRootSet, mainDexClasses.getClasses(), appView.appInfo(), options)
@@ -459,8 +459,6 @@
           ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
           reasonPrinter.run(mainDexApplication);
         }
-        // Add automatic main dex classes to an eventual manual list of classes.
-        application = application.builder().addToMainDexList(mainDexClasses.getClasses()).build();
       }
 
       appView.setAppInfo(new AppInfoWithSubtyping(application));
@@ -484,12 +482,21 @@
             reasonPrinter.run(application);
             // Remove annotations that refer to types that no longer exist.
             new AnnotationRemover(appView.appInfo().withLiveness(), options).run();
+            if (!mainDexClasses.isEmpty()) {
+              // Remove types that no longer exists from the computed main dex list.
+              mainDexClasses = mainDexClasses.prunedCopy(appView.appInfo().withLiveness());
+            }
           }
         } finally {
           timing.end();
         }
       }
 
+      // Add automatic main dex classes to an eventual manual list of classes.
+      if (!options.mainDexKeepRules.isEmpty()) {
+        application = application.builder().addToMainDexList(mainDexClasses.getClasses()).build();
+      }
+
       // Only perform discard-checking if tree-shaking is turned on.
       if (options.enableTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
         new DiscardedChecker(rootSet, application, options).run();
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index bdea79c..d23f3d8 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.13-dev";
+  public static final String LABEL = "1.4.14-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 6914bc1..724faf1 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -381,6 +381,37 @@
     updateFirstStackByJoiningTheSecond(typesOfKept.stack, typesOfRemoved.stack);
   }
 
+  @Override
+  public boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second) {
+    if (!java.util.Objects.equals(first.getLocalsAtEntry(), second.getLocalsAtEntry())) {
+      return false;
+    }
+    // Check that stack at entry is same.
+    List<TypeInfo> firstStack = getTypesAtBlockEntry(first).stack;
+    List<TypeInfo> secondStack = getTypesAtBlockEntry(second).stack;
+    if (firstStack.size() != secondStack.size()) {
+      return false;
+    }
+    for (int i = 0; i < firstStack.size(); i++) {
+      if (firstStack.get(i).getDexType() != secondStack.get(i).getDexType()) {
+        return false;
+      }
+    }
+    // Check if the blocks are catch-handlers, which implies that the exception is transferred
+    // through the stack.
+    if (first.entry().isMoveException() != second.entry().isMoveException()) {
+      return false;
+    }
+    // If both blocks are catch handlers, we require that the exceptions are the same type.
+    if (first.entry().isMoveException()
+        && typeHelper.getTypeInfo(first.entry().outValue()).getDexType()
+            != typeHelper.getTypeInfo(second.entry().outValue()).getDexType()) {
+      return false;
+    }
+
+    return true;
+  }
+
   // Return false if this is not an instruction with the type of outvalue dependent on types of
   // invalues.
   private boolean tryApplyInstructionWithDependentOutType(
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e9966f5..1c04f9d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -993,13 +993,14 @@
 
   public ReferenceTypeLatticeElement createReferenceTypeLatticeElement(
       DexType type, boolean isNullable, AppInfo appInfo) {
+    ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
+    if (typeLattice != null) {
+      return isNullable == typeLattice.isNullable() ? typeLattice
+          : typeLattice.getOrCreateDualLattice();
+    }
     synchronized (type) {
-      ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
-      if (typeLattice != null) {
-        typeLattice = isNullable == typeLattice.isNullable() ? typeLattice
-            : typeLattice.getOrCreateDualLattice();
-        assert typeLattice.isNullable() == isNullable;
-      } else {
+      typeLattice = referenceTypeLatticeElements.get(type);
+      if (typeLattice == null) {
         if (type.isClassType()) {
           if (!type.isUnknown() && type.isInterface()) {
             typeLattice = new ClassTypeLatticeElement(
@@ -1020,9 +1021,11 @@
         }
         referenceTypeLatticeElements.put(type, typeLattice);
       }
-      assert typeLattice.isNullable() == isNullable;
-      return typeLattice;
     }
+    // The call to getOrCreateDualLattice can't be under the DexType synchronized block, since that
+    // can create deadlocks with ClassTypeLatticeElement::getInterfaces (both lock on the lattice).
+    return isNullable == typeLattice.isNullable() ? typeLattice
+        : typeLattice.getOrCreateDualLattice();
   }
 
   private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 73cf07f..17c8d1a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
-import com.google.common.base.Objects;
 import java.util.Arrays;
 import java.util.List;
 
@@ -40,7 +39,7 @@
       }
     }
 
-    if (!Objects.equal(first.getLocalsAtEntry(), second.getLocalsAtEntry())) {
+    if (!allocator.hasEqualTypesAtEntry(first, second)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index ade48f3..7754a14 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -197,8 +197,11 @@
     //      of class inlining
     //
 
-    if (eligibleClassDefinition.instanceFields().length > 0 ||
-        !eligibleClassDefinition.accessFlags.isFinal()) {
+    if (eligibleClassDefinition.instanceFields().length > 0) {
+      return false;
+    }
+    if (eligibleClassDefinition.type.hasSubtypes()) {
+      assert !eligibleClassDefinition.accessFlags.isFinal();
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index c971a14..40d31d2 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -3172,6 +3172,11 @@
   }
 
   @Override
+  public boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second) {
+    return java.util.Objects.equals(first.getLocalsAtEntry(), second.getLocalsAtEntry());
+  }
+
+  @Override
   public void addNewBlockToShareIdenticalSuffix(
       BasicBlock block, int suffixSize, List<BasicBlock> predsBeforeSplit) {
     // Intentionally empty, we don't need to track suffix sharing in this allocator.
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index 1314b3f..43b41e5 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -17,6 +17,8 @@
 
   void mergeBlocks(BasicBlock kept, BasicBlock removed);
 
+  boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second);
+
   // Call before removing the suffix from the preds. Block is used only as a key.
   void addNewBlockToShareIdenticalSuffix(
       BasicBlock block, int suffixSize, List<BasicBlock> predsBeforeSplit);
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
index 3869db6..565a5df 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
@@ -7,13 +7,19 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class MainDexClasses {
 
+  public static MainDexClasses NONE = new MainDexClasses(ImmutableSet.of(), ImmutableSet.of());
+
   public static class Builder {
     public final AppInfo appInfo;
     public final Set<DexType> roots = Sets.newIdentityHashSet();
@@ -23,6 +29,12 @@
       this.appInfo = appInfo;
     }
 
+    public Builder addRoot(DexType type) {
+      assert isProgramClass(type) : type.toSourceString();
+      roots.add(type);
+      return this;
+    }
+
     public Builder addRoots(Collection<DexType> rootSet) {
       assert rootSet.stream().allMatch(this::isProgramClass);
       this.roots.addAll(rootSet);
@@ -50,21 +62,26 @@
   }
 
   // The classes in the root set.
-  private final Set<DexType> rootSet;
-  // Additional dependencies (direct dependencise and runtime annotations with enums).
+  private final Set<DexType> roots;
+  // Additional dependencies (direct dependencies and runtime annotations with enums).
   private final Set<DexType> dependencies;
   // All main dex classes.
   private final Set<DexType> classes;
 
-  private MainDexClasses(Set<DexType> rootSet, Set<DexType> dependencies) {
-    assert Sets.intersection(rootSet, dependencies).isEmpty();
-    this.rootSet = Collections.unmodifiableSet(rootSet);
+  private MainDexClasses(Set<DexType> roots, Set<DexType> dependencies) {
+    assert Sets.intersection(roots, dependencies).isEmpty();
+    this.roots = Collections.unmodifiableSet(roots);
     this.dependencies = Collections.unmodifiableSet(dependencies);
-    this.classes = Sets.union(rootSet, dependencies);
+    this.classes = Sets.union(roots, dependencies);
+  }
+
+  public boolean isEmpty() {
+    assert !roots.isEmpty() || dependencies.isEmpty();
+    return roots.isEmpty();
   }
 
   public Set<DexType> getRoots() {
-    return rootSet;
+    return roots;
   }
 
   public Set<DexType> getDependencies() {
@@ -75,6 +92,24 @@
     return classes;
   }
 
+  private void collectTypesMatching(
+      Set<DexType> types, Predicate<DexType> predicate, Consumer<DexType> consumer) {
+    types.forEach(
+        type -> {
+          if (predicate.test(type)) {
+            consumer.accept(type);
+          }
+        });
+  }
+
+  public MainDexClasses prunedCopy(AppInfoWithLiveness appInfo) {
+    Builder builder = builder(appInfo);
+    Predicate<DexType> wasPruned = appInfo::wasPruned;
+    collectTypesMatching(roots, wasPruned.negate(), builder::addRoot);
+    collectTypesMatching(dependencies, wasPruned.negate(), builder::addDependency);
+    return builder.build();
+  }
+
   public static Builder builder(AppInfo appInfo) {
     return new Builder(appInfo);
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ee0e8af..4404bae 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -10,10 +10,12 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class R8TestBuilder
     extends TestShrinkerBuilder<
@@ -64,6 +66,28 @@
     return self();
   }
 
+  public R8TestBuilder addMainDexRules(Collection<String> rules) {
+    builder.addMainDexRules(new ArrayList<>(rules), Origin.unknown());
+    return self();
+  }
+
+  public R8TestBuilder addMainDexRules(String... rules) {
+    return addMainDexRules(Arrays.asList(rules));
+  }
+
+  public R8TestBuilder addMainDexClassRules(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      addMainDexRules("-keep class " + clazz.getTypeName());
+    }
+    return self();
+  }
+
+  public R8TestBuilder addMainDexListClasses(Class<?>... classes) {
+    builder.addMainDexClasses(
+        Arrays.stream(classes).map(Class::getTypeName).collect(Collectors.toList()));
+    return self();
+  }
+
   public R8TestBuilder enableInliningAnnotations() {
     if (!enableInliningAnnotations) {
       enableInliningAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 3042cbe..4821c25 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -47,6 +47,7 @@
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -581,7 +582,7 @@
 
   void execute(String testName,
       String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) throws IOException {
-
+    Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
     boolean expectedToFail = expectedToFail(testName);
     try {
       String output = ToolHelper.runArtNoVerificationErrors(
@@ -592,7 +593,9 @@
               builder.appendProgramArgument(arg);
             }
           });
-      if (!skipRunningOnJvm(testName) && !ToolHelper.compareAgaintsGoldenFiles()) {
+      if (!expectedToFail
+          && !skipRunningOnJvm(testName)
+          && !ToolHelper.compareAgaintsGoldenFiles()) {
         ArrayList<String> javaArgs = Lists.newArrayList(args);
         javaArgs.add(0, qualifiedMainClass);
         ToolHelper.ProcessResult javaResult =
@@ -606,7 +609,7 @@
             output.replace("\r", "").equals(javaResult.stdout.replace("\r", "")));
       }
     } catch (Throwable t) {
-      assert expectedToFail && !ToolHelper.compareAgaintsGoldenFiles();
+      assert expectedToFail;
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 34ffcaa..c750ded 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -96,7 +96,30 @@
         return this;
       }
     }
-    fail("No warning matches " + matcher.toString());
+    StringBuilder builder = new StringBuilder("No warning matches " + matcher.toString());
+    builder.append(System.lineSeparator());
+    if (getDiagnosticMessages().getWarnings().size() == 0) {
+      builder.append("There where no warnings.");
+    } else {
+      builder.append("There where " + getDiagnosticMessages().getWarnings().size() + " warnings:");
+      builder.append(System.lineSeparator());
+      for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
+        builder.append(getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage());
+        builder.append(System.lineSeparator());
+      }
+    }
+    fail(builder.toString());
+    return this;
+  }
+
+  public TestCompileResult<RR> assertNoWarningMessageThatMatches(Matcher<String> matcher) {
+    assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
+    for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
+      String message = getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage();
+      if (matcher.matches(message)) {
+        fail("The warning: \"" + message + "\" + matches " + matcher + ".");
+      }
+    }
     return this;
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
new file mode 100644
index 0000000..b778c41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -0,0 +1,83 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import java.util.Set;
+import org.junit.Test;
+
+public class NonidenticalCatchHandlerTest extends TestBase {
+
+  private static class TestClass {
+    public void foo(Object a) {
+      int x = 0;
+      try {
+        x = ((String)a).length();
+      } catch (ClassCastException e) {
+        x = -1;
+      } catch (NullPointerException e) {
+        x = -1;
+      }
+      System.out.println(x);
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+    byte[] inputBytes = ToolHelper.getClassAsBytes(TestClass.class);
+    AndroidApp inputApp =
+        AndroidApp.builder()
+            .addClassProgramData(inputBytes, Origin.unknown())
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .build();
+    assertEquals(2, countCatchHandlers(inputApp));
+    AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+    assertEquals(1, countCatchHandlers(outputDexApp));
+    AndroidApp outputCfApp =
+        ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+    // ClassCastException and NullPointerException will not have same type on stack.
+    assertEquals(2, countCatchHandlers(outputCfApp));
+  }
+
+  private int countCatchHandlers(AndroidApp inputApp) throws Exception {
+    CodeInspector inspector = new CodeInspector(inputApp);
+    DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
+    Code code = dexClass.virtualMethods()[0].getCode();
+    if (code.isCfCode()) {
+      CfCode cfCode = code.asCfCode();
+      Set<CfLabel> targets = Sets.newIdentityHashSet();
+      for (CfTryCatch tryCatch : cfCode.getTryCatchRanges()) {
+        targets.addAll(tryCatch.targets);
+      }
+      return targets.size();
+    } else if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      IntSet targets = new IntOpenHashSet();
+      for (Try aTry : dexCode.tries) {
+        targets.add(aTry.handlerOffset);
+      }
+      return targets.size();
+    } else {
+      throw new Unimplemented(code.getClass().getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 5ed3cc5..12d0a78 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -51,6 +51,11 @@
     }
 
     @Override
+    public boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second) {
+      return false;
+    }
+
+    @Override
     public void addNewBlockToShareIdenticalSuffix(
         BasicBlock block, int suffixSize, List<BasicBlock> predsBeforeSplit) {
       // Intentionally empty, we don't need to track suffix sharing in this allocator.
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
new file mode 100644
index 0000000..e6ca563
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -0,0 +1,101 @@
+// 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.maindexlist.warnings;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+public class MainDexWarningsTest extends TestBase {
+
+  private List<Class<?>> testClasses = ImmutableList.of(Main.class, Static.class, Static2.class);
+  private Class<?> mainClass = Main.class;
+
+  private void classStaticGone(CodeInspector inspector) {
+    assertThat(inspector.clazz(Static.class), CoreMatchers.not(isPresent()));
+    assertThat(inspector.clazz(Static2.class), CoreMatchers.not(isPresent()));
+  }
+
+  @Test
+  public void testNoWarningFromMainDexRules() throws Exception {
+    testForR8(Backend.DEX)
+        .setMinApi(AndroidApiLevel.K)
+        .enableInliningAnnotations()
+        .addProgramClasses(testClasses)
+        .addKeepMainRule(mainClass)
+        // Include main dex rule for class Static.
+        .addMainDexClassRules(Main.class, Static.class)
+        .compile()
+        .inspect(this::classStaticGone)
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testWarningFromManualMainDexList() throws Exception {
+    testForR8(Backend.DEX)
+        .setMinApi(AndroidApiLevel.K)
+        .enableInliningAnnotations()
+        .addProgramClasses(testClasses)
+        .addKeepMainRule(mainClass)
+        // Include explicit main dex entry for class Static.
+        .addMainDexListClasses(Static.class)
+        .compile()
+        .inspect(this::classStaticGone)
+        .assertOnlyWarnings()
+        .assertWarningMessageThatMatches(containsString("Application does not contain"))
+        .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
+        .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"));
+  }
+
+  @Test
+  public void testWarningFromManualMainDexListWithRuleAsWell() throws Exception {
+    testForR8(Backend.DEX)
+        .setMinApi(AndroidApiLevel.K)
+        .enableInliningAnnotations()
+        .addProgramClasses(testClasses)
+        .addKeepMainRule(mainClass)
+        // Include explicit main dex entry for class Static.
+        .addMainDexListClasses(Main.class, Static.class)
+        // Include main dex rule for class Static2.
+        .addMainDexClassRules(Static2.class)
+        .compile()
+        .inspect(this::classStaticGone)
+        .assertOnlyWarnings()
+        .assertWarningMessageThatMatches(containsString("Application does not contain"))
+        .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
+        .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"))
+        .assertNoWarningMessageThatMatches(containsString(Static2.class.getTypeName()));
+  }
+}
+
+class Main {
+  public static void main(String[] args) {
+    System.out.println(Static.m());
+    System.out.println(Static2.m());
+  }
+}
+
+class Static {
+  @ForceInline
+  public static int m() {
+    return 1;
+  }
+}
+
+class Static2 {
+  @ForceInline
+  public static int m() {
+    return 1;
+  }
+}