Merge "Virtualize some methods from TypeLatticeElement"
diff --git a/build.gradle b/build.gradle
index e6432e4..580bcaf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -478,13 +478,16 @@
     }
 }
 
-static configureRelocations(ShadowJar task) {
+static mergeServiceFiles(ShadowJar task) {
     // Everything under META-INF is not included by default.
     // Should include before 'relocate' so that the service file path and its content
     // are properly relocated as well.
     task.mergeServiceFiles {
         include 'META-INF/services/*'
     }
+}
+
+static configureRelocations(ShadowJar task) {
     task.relocate('com.google.common', 'com.android.tools.r8.com.google.common')
     task.relocate('com.google.gson', 'com.android.tools.r8.com.google.gson')
     task.relocate('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty')
@@ -500,7 +503,10 @@
 
 task repackageDeps(type: ShadowJar) {
     configurations = [project.configurations.compile]
-    configureRelocations(it)
+    mergeServiceFiles(it)
+    if (!project.hasProperty('lib_no_relocate')) {
+        configureRelocations(it)
+    }
     exclude { it.getRelativePath().getPathString() == "module-info.class" }
     exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
     baseName 'deps'
@@ -508,7 +514,10 @@
 
 task repackageSources(type: ShadowJar) {
     from sourceSets.main.output
-    configureRelocations(it)
+    mergeServiceFiles(it)
+    if (!project.hasProperty('lib_no_relocate')) {
+        configureRelocations(it)
+    }
     baseName 'sources'
 }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 96e1e99..7b24770 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -650,6 +650,9 @@
 
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
     if (internal.debug) {
+      internal.proguardConfiguration.getKeepAttributes().lineNumberTable = true;
+      internal.proguardConfiguration.getKeepAttributes().localVariableTable = true;
+      internal.proguardConfiguration.getKeepAttributes().localVariableTypeTable = true;
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       internal.enableInlining = false;
       internal.enableClassInlining = false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 3df31fd..22790b4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -412,6 +412,10 @@
     return null;
   }
 
+  public boolean isExternalizable(AppInfo appInfo) {
+    return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.externalizableType);
+  }
+
   public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
     return Arrays.stream(staticFields())
         .anyMatch(field -> !field.getStaticValue().mayTriggerAllocation());
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index fa856cb..6c6fda4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -206,7 +206,10 @@
     if (localsChanged()) {
       assert emittedPc != pc;
       int pcDelta = emittedPc == NO_PC_INFO ? pc : pc - emittedPc;
-      events.add(factory.createAdvancePC(pcDelta));
+      assert pcDelta > 0 || emittedPc == NO_PC_INFO;
+      if (pcDelta > 0) {
+        events.add(factory.createAdvancePC(pcDelta));
+      }
       emittedPc = pc;
       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
       pendingLocalChanges = false;
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 7254577..90a098a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -256,6 +256,7 @@
   public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
+  public final DexType externalizableType = createType("Ljava/io/Externalizable;");
   public final DexType comparableType = createType("Ljava/lang/Comparable;");
 
   public final DexMethod metafactoryMethod =
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index fe63f46..138e07e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -37,6 +37,9 @@
    */
   private Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
 
+  // Caching what interfaces this type is implementing. This includes super-interface hierarchy.
+  private Set<DexType> implementedInterfaces;
+
   DexType(DexString descriptor) {
     assert !descriptor.toString().contains(".");
     this.descriptor = descriptor;
@@ -234,9 +237,12 @@
    * @return a set of interfaces of {@link DexType}.
    */
   public Set<DexType> implementedInterfaces(AppInfo appInfo) {
-    Set<DexType> interfaces = Sets.newIdentityHashSet();
-    implementedInterfaces(appInfo, interfaces);
-    return interfaces;
+    if (implementedInterfaces == null) {
+      Set<DexType> interfaces = Sets.newIdentityHashSet();
+      implementedInterfaces(appInfo, interfaces);
+      implementedInterfaces = interfaces;
+    }
+    return implementedInterfaces;
   }
 
   private void implementedInterfaces(AppInfo appInfo, Set<DexType> interfaces) {
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 bfb2b95..de42c18 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -265,13 +265,18 @@
     if (item.isDexClass()) {
       DexClass clazz = item.asDexClass();
       workList.add(Action.markInstantiated(clazz, reason));
-      if (forceProguardCompatibility && clazz.hasDefaultInitializer()) {
-        ProguardKeepRule compatRule =
+      if (clazz.hasDefaultInitializer()) {
+        if (forceProguardCompatibility) {
+          ProguardKeepRule compatRule =
             ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
-        proguardCompatibilityWorkList.add(
-            Action.markMethodLive(
-                clazz.getDefaultInitializer(),
-                KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
+          proguardCompatibilityWorkList.add(
+              Action.markMethodLive(
+                  clazz.getDefaultInitializer(),
+                  KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
+        }
+        if (clazz.isExternalizable(appInfo)) {
+          workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+        }
       }
     } else if (item.isDexEncodedField()) {
       workList.add(Action.markFieldKept(item.asDexEncodedField(), reason));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index e7351f2..08a220f 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -456,7 +456,7 @@
     DexType collection = factory.createType("Ljava/util/Collection;");
     DexType set = factory.createType("Ljava/util/Set;");
     DexType list = factory.createType("Ljava/util/List;");
-    DexType serializable = factory.createType("Ljava/io/Serializable;");
+    DexType serializable = factory.serializableType;
 
     Set<DexType> lub = computeLeastUpperBoundOfInterfaces(appInfo,
         ImmutableSet.of(set), ImmutableSet.of(list));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index 1eddbd3..d7a5732 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -29,6 +29,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -76,6 +77,7 @@
     assertThat(classSubject, isPresent());
   }
 
+  @Ignore("todo: jsjeon")
   @Test
   public void test_differentLocals() throws Throwable {
     ClassSubject classSubject = inspector.clazz(MAIN);
@@ -129,6 +131,7 @@
     );
   }
 
+  @Ignore("todo: jsjeon")
   @Test
   public void test_sameLocal() throws Throwable {
     ClassSubject classSubject = inspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
index 275d2e9..96ce54a 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
@@ -3,33 +3,57 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.debuginfo.DebugInfoInspector;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.keepattributes.TestKeepAttributes;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class KeepAttributesTest extends TestBase {
 
-  public static final Class CLASS = TestKeepAttributes.class;
+  private static final Class CLASS = TestKeepAttributes.class;
+
+  @Parameters(name = "{0}")
+  public static Backend[] parameters() {
+    return new Backend[] { Backend.CF, Backend.DEX};
+  }
+
+  private final Backend backend;
+
+  public KeepAttributesTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void keepAllAttributesInDebugMode()
+      throws ExecutionException, CompilationFailedException, IOException {
+    List<String> keepRules = ImmutableList.of(
+        "-keep class ** { *; }"
+    );
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.DEBUG);
+    assertTrue(mainMethod.hasLineNumberTable());
+    assertTrue(mainMethod.hasLocalVariableTable());
+  }
 
   @Test
   public void discardAllAttributes()
@@ -37,10 +61,9 @@
     List<String> keepRules = ImmutableList.of(
         "-keep class ** { *; }"
     );
-    CodeInspector inspector = compile(keepRules);
-    DebugInfoInspector debugInfo = debugInfoForMain(inspector);
-    checkLineNumbers(false, debugInfo);
-    checkLocals(false, debugInfo);
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+    assertFalse(mainMethod.hasLineNumberTable());
+    assertFalse(mainMethod.hasLocalVariableTable());
   }
 
   @Test
@@ -50,10 +73,9 @@
         "-keep class ** { *; }",
         "-keepattributes " + ProguardKeepAttributes.LINE_NUMBER_TABLE
     );
-    CodeInspector inspector = compile(keepRules);
-    DebugInfoInspector debugInfo = debugInfoForMain(inspector);
-    checkLineNumbers(true, debugInfo);
-    checkLocals(false, debugInfo);
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+    assertTrue(mainMethod.hasLineNumberTable());
+    assertFalse(mainMethod.hasLocalVariableTable());
   }
 
   @Test
@@ -66,10 +88,10 @@
             + ", "
             + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
     );
-    CodeInspector inspector = compile(keepRules);
-    DebugInfoInspector debugInfo = debugInfoForMain(inspector);
-    checkLineNumbers(true, debugInfo);
-    checkLocals(true, debugInfo);
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+    assertTrue(mainMethod.hasLineNumberTable());
+    // Locals are never included in release builds.
+    assertFalse(mainMethod.hasLocalVariableTable());
   }
 
   @Test
@@ -80,7 +102,7 @@
     );
     // Compiling with a keep rule for locals but no line results in an error in R8.
     try {
-      compile(keepRules);
+      compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
     } catch (CompilationFailedException e) {
       assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LOCAL_VARIABLE_TABLE));
       assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LINE_NUMBER_TABLE));
@@ -89,34 +111,23 @@
     fail("Expected error");
   }
 
-  private CodeInspector compile(List<String> keepRules)
+  private MethodSubject compileRunAndGetMain(List<String> keepRules, CompilationMode mode)
       throws CompilationFailedException, IOException, ExecutionException {
-    Path dexOut = temp.getRoot().toPath().resolve("dex.zip");
-    R8.run(R8Command.builder()
-        .setMode(CompilationMode.DEBUG)
-        .addProgramFiles(ToolHelper.getClassFileForTestClass(CLASS))
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-        .addProguardConfiguration(keepRules, Origin.unknown())
-        .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(dexOut))
-        .build());
-    ToolHelper.runArtRaw(dexOut.toString(), CLASS.getCanonicalName());
-    return new CodeInspector(dexOut);
+    AndroidAppConsumers sink = new AndroidAppConsumers();
+    R8.run(
+        R8Command.builder()
+            .setMode(mode)
+            .addProgramFiles(
+                ToolHelper.getClassFilesForTestDirectory(
+                    ToolHelper.getClassFileForTestClass(CLASS).getParent()))
+            .addLibraryFiles(runtimeJar(backend))
+            .addProguardConfiguration(keepRules, Origin.unknown())
+            .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+            .build());
+    AndroidApp app = sink.build();
+    CodeInspector codeInspector = new CodeInspector(app);
+    runOnVM(app, CLASS.getTypeName(), backend);
+    return codeInspector.clazz(CLASS).mainMethod();
   }
 
-  private DebugInfoInspector debugInfoForMain(CodeInspector inspector) {
-    return new DebugInfoInspector(
-        inspector,
-        CLASS.getCanonicalName(),
-        new MethodSignature("main", "void", Collections.singleton("java.lang.String[]")));
-  }
-
-  private void checkLineNumbers(boolean expected, DebugInfoInspector debugInfo) {
-    assertEquals("Expected " + (expected ? "line entries" : "no line entries"),
-        expected, debugInfo.getEntries().stream().anyMatch(e -> e.lineEntry));
-  }
-
-  private void checkLocals(boolean expected, DebugInfoInspector debugInfo) {
-    assertEquals("Expected " + (expected ? "locals" : "no locals"),
-        expected, debugInfo.getEntries().stream().anyMatch(e -> !e.locals.isEmpty()));
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index 952bae6..cf14d8b 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -88,13 +88,13 @@
       case PROGUARD6_THEN_D8:
         return runProguard6AndD8(programClasses, proguardConfig, proguardMap);
       case R8_COMPAT:
-        return runR8Compat(programClasses, proguardConfig, Backend.DEX);
+        return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.DEX);
       case R8_COMPAT_CF:
-        return runR8Compat(programClasses, proguardConfig, Backend.CF);
+        return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.CF);
       case R8:
-        return runR8(programClasses, proguardConfig, Backend.DEX);
+        return runR8(programClasses, proguardConfig, proguardMap, Backend.DEX);
       case R8_CF:
-        return runR8(programClasses, proguardConfig, Backend.CF);
+        return runR8(programClasses, proguardConfig, proguardMap, Backend.CF);
     }
     throw new IllegalArgumentException("Unknown shrinker: " + mode);
   }
@@ -126,34 +126,45 @@
     throw new IllegalArgumentException("Unknown shrinker: " + mode);
   }
 
-  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+  protected AndroidApp runR8(
+      List<Class> programClasses,
+      String proguardConfig,
+      Path proguardMap,
+      Backend backend)
       throws Exception {
-    return runR8(programClasses, proguardConfig, null, backend);
+    return runR8(programClasses, proguardConfig, proguardMap, null, backend);
   }
 
   protected AndroidApp runR8(
       List<Class> programClasses,
       String proguardConfig,
+      Path proguardMap,
       Consumer<InternalOptions> configure,
       Backend backend)
       throws Exception {
     AndroidApp app = readClassesAndRuntimeJar(programClasses, backend);
     R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
     ToolHelper.allowTestProguardOptions(builder);
-    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    builder.addProguardConfiguration(
+        ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
     return ToolHelper.runR8(builder.build(), configure);
   }
 
   protected CodeInspector inspectR8Result(
       List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
-    return new CodeInspector(runR8(programClasses, proguardConfig, backend));
+    return new CodeInspector(runR8(programClasses, proguardConfig, null, backend));
   }
 
   protected AndroidApp runR8Compat(
-      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+      List<Class> programClasses,
+      String proguardConfig,
+      Path proguardMap,
+      Backend backend)
+      throws Exception {
     CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
     ToolHelper.allowTestProguardOptions(builder);
-    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    builder.addProguardConfiguration(
+        ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
     programClasses.forEach(
         clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
     if (backend == Backend.DEX) {
@@ -169,7 +180,7 @@
 
   protected CodeInspector inspectR8CompatResult(
       List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
-    return new CodeInspector(runR8Compat(programClasses, proguardConfig, backend));
+    return new CodeInspector(runR8Compat(programClasses, proguardConfig, null, backend));
   }
 
   protected AndroidApp runProguard5(
@@ -306,4 +317,8 @@
       assertThat(c, not(isPresent()));
     }
   }
+
+  private String toPrintMappingRule(Path proguardMap) {
+    return proguardMap == null ? "" : "-printmapping " + proguardMap.toAbsolutePath();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index dcb1970c..e20b80a 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -264,7 +264,9 @@
   @Parameterized.Parameters(name = "Shrinker: {0}")
   public static Collection<Object> data() {
     return ImmutableList.of(
-        Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+        Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6,
+        Shrinker.R8_COMPAT, Shrinker.R8_COMPAT_CF,
+        Shrinker.R8, Shrinker.R8_CF);
   }
 
   public ExternalizableTest(Shrinker shrinker) {
@@ -273,11 +275,6 @@
 
   @Test
   public void testExternalizable() throws Exception {
-    // TODO(b/116735204): R8 should keep default ctor() of classes that implement Externalizable
-    if (shrinker.isR8()) {
-      return;
-    }
-
     String javaOutput = runOnJava(ExternalizableTestMain.class);
 
     List<String> config = ImmutableList.of(
@@ -300,6 +297,12 @@
       assertEquals(javaOutput.trim(), output.trim());
     }
 
+    // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+    //   1.11 The Externalizable Interface
+    //   ...
+    //   The class of an Externalizable object must do the following:
+    //   ...
+    //     * Have a public no-arg constructor
     CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
     ClassSubject classSubject = codeInspector.clazz(ExternalizableDataClass.class);
     assertThat(classSubject, isPresent());
@@ -336,6 +339,12 @@
       assertEquals(javaOutput.trim(), output.trim());
     }
 
+    // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+    //   1.10 The Serializable Interface
+    //   ...
+    //   A Serializable class must do the following:
+    //   ...
+    //     * Have access to the no-arg constructor of its first non-serializable superclass
     CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
     ClassSubject classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
     assertThat(classSubject, isPresent());
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 5ea7769..89d5079 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
@@ -16,6 +16,7 @@
 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;
@@ -45,10 +46,12 @@
   }
 
   @Override
-  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+  protected AndroidApp runR8(
+      List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
       throws Exception {
     // Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
-    return runR8(programClasses, proguardConfig, o -> o.enableInlining = false, backend);
+    return runR8(
+        programClasses, proguardConfig, proguardMap, o -> o.enableInlining = false, backend);
   }
 
   @Test
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 b003b7b..165a68f 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
@@ -89,9 +89,10 @@
   }
 
   @Override
-  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+  protected AndroidApp runR8(
+      List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
       throws Exception {
-    return super.runR8(programClasses, proguardConfig, this::configure, backend);
+    return super.runR8(programClasses, proguardConfig, proguardMap, this::configure, backend);
   }
 
   private void check(AndroidApp app) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index 1396be9..adee8cf 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -78,4 +78,14 @@
   public String getFinalSignatureAttribute() {
     return null;
   }
+
+  @Override
+  public boolean hasLineNumberTable() {
+    return false;
+  }
+
+  @Override
+  public boolean hasLocalVariableTable() {
+    return false;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 3fe8186..f205594 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -4,12 +4,22 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import java.util.Iterator;
+import java.util.ListIterator;
 import java.util.function.Predicate;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.LineNumberNode;
 
 public class FoundMethodSubject extends MethodSubject {
 
@@ -134,6 +144,69 @@
   }
 
   @Override
+  public boolean hasLineNumberTable() {
+    Code code = getMethod().getCode();
+    if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      if (dexCode.getDebugInfo() != null) {
+        for (DexDebugEvent event : dexCode.getDebugInfo().events) {
+          if (event instanceof DexDebugEvent.Default) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+    if (code.isCfCode()) {
+      for (CfInstruction insn : code.asCfCode().getInstructions()) {
+        if (insn instanceof CfPosition) {
+          return true;
+        }
+      }
+      return false;
+    }
+    if (code.isJarCode()) {
+      ListIterator<AbstractInsnNode> it = code.asJarCode().getNode().instructions.iterator();
+      while (it.hasNext()) {
+        if (it.next() instanceof LineNumberNode) {
+          return true;
+        }
+      }
+      return false;
+    }
+    throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+  }
+
+  @Override
+  public boolean hasLocalVariableTable() {
+    Code code = getMethod().getCode();
+    if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      if (dexCode.getDebugInfo() != null) {
+        for (DexString parameter : dexCode.getDebugInfo().parameters) {
+          if (parameter != null) {
+            return true;
+          }
+        }
+        for (DexDebugEvent event : dexCode.getDebugInfo().events) {
+          if (event instanceof DexDebugEvent.StartLocal) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+    if (code.isCfCode()) {
+      return !code.asCfCode().getLocalVariables().isEmpty();
+    }
+    if (code.isJarCode()) {
+      return code.asJarCode().getNode().localVariables != null
+          && !code.asJarCode().getNode().localVariables.isEmpty();
+    }
+    throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+  }
+
+  @Override
   public String toString() {
     return dexMethod.toSourceString();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 6eaaf5f..55030ca 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -32,4 +32,8 @@
       Predicate<InstructionSubject> filter) {
     return null;
   }
+
+  public abstract boolean hasLineNumberTable();
+
+  public abstract boolean hasLocalVariableTable();
 }
diff --git a/tools/build_r8lib.py b/tools/build_r8lib.py
index 4b7473e..5af6d4d 100755
--- a/tools/build_r8lib.py
+++ b/tools/build_r8lib.py
@@ -9,6 +9,7 @@
 '''
 
 import argparse
+import gradle
 import os
 import subprocess
 import toolhelper
@@ -16,39 +17,70 @@
 
 parser = argparse.ArgumentParser(description=__doc__.strip(),
                                  formatter_class=argparse.RawTextHelpFormatter)
-
-SAMPLE_JAR = os.path.join(utils.REPO_ROOT, 'tests/d8_api_usage_sample.jar')
-R8LIB_JAR = os.path.join(utils.LIBS, 'r8lib.jar')
-R8LIB_MAP_FILE = os.path.join(utils.LIBS, 'r8lib-map.txt')
+parser.add_argument('-e', '--exclude_deps', action='store_true',
+                    help='Create lib jar without dependencies')
+parser.add_argument('-k', '--keep', default=utils.R8LIB_KEEP_RULES,
+                    help='Keep rules file for lib')
+parser.add_argument('-n', '--no_relocate', action='store_true',
+                    help='Create lib jar without relocating libraries')
+parser.add_argument('-o', '--out', default=None,
+                    help='Output for built library')
+parser.add_argument('-t', '--target', default='r8',
+                    help='Compile target for library')
 
 API_LEVEL = 26
-ANDROID_JAR = 'third_party/android_jar/lib-v%s/android.jar' % API_LEVEL
+DEPS_JAR = os.path.join(utils.LIBS, 'deps.jar')
+SAMPLE_JAR = os.path.join(utils.REPO_ROOT, 'tests', 'd8_api_usage_sample.jar')
 
-
-def build_r8lib(output_path=None, output_map=None, **kwargs):
+def build_r8lib(target, exclude_deps, no_relocate, keep_rules_path,
+    output_path, **kwargs):
+  # Clean the build directory to ensure no repackaging of any existing
+  # lib or deps.
+  gradle.RunGradle(['clean'])
+  lib_args = [target]
+  deps_args = ['repackageDeps']
+  if exclude_deps:
+    lib_args.append('-Pexclude_deps')
+  if no_relocate:
+    lib_args.append('-Plib_no_relocate')
+    deps_args.append('-Plib_no_relocate')
+  # Produce the r8lib target to be processed later.
+  gradle.RunGradle(lib_args)
+  target_lib = os.path.join(utils.LIBS, target + '.jar')
+  temp_lib = os.path.join(utils.LIBS, target + '_to_process.jar')
+  os.rename(target_lib, temp_lib)
+  # Produce the dependencies needed for running r8 on lib.jar.
+  gradle.RunGradle(deps_args)
+  temp_deps = os.path.join(utils.LIBS, target + 'lib_deps.jar')
+  os.rename(DEPS_JAR, temp_deps)
+  # Produce R8 for compiling lib
   if output_path is None:
-    output_path = R8LIB_JAR
-  if output_map is None:
-    output_map = R8LIB_MAP_FILE
+    output_path = target + 'lib.jar'
+  output_map_path = os.path.splitext(output_path)[0] + '.map'
   toolhelper.run(
       'r8',
       ('--release',
        '--classfile',
        '--lib', utils.RT_JAR,
-       utils.R8_JAR,
+       '--lib', temp_deps,
+       temp_lib,
        '--output', output_path,
-       '--pg-conf', utils.R8LIB_KEEP_RULES,
-       '--pg-map-output', output_map),
+       '--pg-conf', keep_rules_path,
+       '--pg-map-output', output_map_path),
       **kwargs)
+  if exclude_deps:
+    return [output_path, temp_deps]
+  else:
+    return [output_path]
 
 
-def test_d8sample():
+def test_d8sample(paths):
   with utils.TempDir() as path:
-    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
             'com.android.tools.apiusagesample.D8ApiUsageSample',
             '--output', path,
             '--min-api', str(API_LEVEL),
-            '--lib', ANDROID_JAR,
+            '--lib', utils.get_android_jar(API_LEVEL),
             '--classpath', utils.R8_JAR,
             '--main-dex-list', '/dev/null',
             os.path.join(utils.BUILD, 'test/examples/hello.jar')]
@@ -56,30 +88,30 @@
     subprocess.check_call(args)
 
 
-def test_r8command():
+def test_r8command(paths):
   with utils.TempDir() as path:
-    # SAMPLE_JAR and R8LIB_JAR should not have any classes in common, since e.g.
-    # R8CommandParser should have been minified in R8LIB_JAR.
-    # Just in case R8CommandParser is also present in R8LIB_JAR, we put
+    # SAMPLE_JAR and LIB_JAR should not have any classes in common, since e.g.
+    # R8CommandParser should have been minified in LIB_JAR.
+    # Just in case R8CommandParser is also present in LIB_JAR, we put
     # SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
-    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
             'com.android.tools.r8.R8CommandParser',
             '--output', path + "/output.zip",
             '--min-api', str(API_LEVEL),
-            '--lib', ANDROID_JAR,
+            '--lib', utils.get_android_jar(API_LEVEL),
             '--main-dex-list', '/dev/null',
             os.path.join(utils.BUILD, 'test/examples/hello.jar')]
     utils.PrintCmd(args)
     subprocess.check_call(args)
 
 
-def test_r8cfcommand():
+def test_r8cfcommand(paths):
   with utils.TempDir() as path:
-    # SAMPLE_JAR and R8LIB_JAR should not have any classes in common, since e.g.
-    # R8CommandParser should have been minified in R8LIB_JAR.
-    # Just in case R8CommandParser is also present in R8LIB_JAR, we put
+    # SAMPLE_JAR and LIB_JAR should not have any classes in common, since e.g.
+    # R8CommandParser should have been minified in LIB_JAR.
+    # Just in case R8CommandParser is also present in LIB_JAR, we put
     # SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
-    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
             'com.android.tools.r8.R8CommandParser',
             '--classfile',
             '--output', path + "/output.jar",
@@ -91,12 +123,16 @@
 
 def main():
   # Handle --help
-  parser.parse_args()
-
-  build_r8lib()
-  test_d8sample()
-  test_r8command()
-  test_r8cfcommand()
+  args = parser.parse_args()
+  output_paths = build_r8lib(
+      args.target, args.exclude_deps, args.no_relocate, args.keep, args.out)
+  if args.target == 'r8':
+    gradle.RunGradle(['buildExampleJars'])
+    test_r8command(output_paths)
+    test_r8cfcommand(output_paths)
+  if args.target == 'd8':
+    gradle.RunGradle(['buildExampleJars'])
+    test_d8sample(output_paths)
 
 
 if __name__ == '__main__':