Merge "Remove ProguardConfiguration.optimizationPasses"
diff --git a/build.gradle b/build.gradle
index 722106c..085666a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -51,6 +51,12 @@
         }
         output.resourcesDir = 'build/classes/debugTestResourcesJava8'
     }
+    debugTestResourcesKotlin {
+        java {
+            srcDirs = ['src/test/debugTestResourcesKotlin']
+        }
+        output.resourcesDir = 'build/classes/debugTestResourcesKotlin'
+    }
     examples {
         java {
             srcDirs = ['src/test/examples', 'build/generated/source/proto/examples/javalite/' ]
@@ -127,6 +133,7 @@
     supportLibs 'com.android.support:support-v4:25.4.0'
     supportLibs 'junit:junit:4.12'
     supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
+    debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.1.3'
 }
 
 protobuf {
@@ -172,6 +179,7 @@
                 "jdwp-tests.tar.gz",
                 "jasmin.tar.gz",
                 "jctf.tar.gz",
+                "kotlin.tar.gz",
                 "android_cts_baseline.tar.gz",
         ],
         // All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
@@ -523,11 +531,29 @@
         from "build/test/debugTestResourcesJava8/classes"
         include "**/*.class"
     }
+    def kotlinResourcesDir = file("src/test/debugTestResourcesKotlin")
+    def kotlinHostJar = "debug_test_resources_kotlin.jar"
+    task "jar_debugTestResourcesKotlin"(type: Exec) {
+        if (OperatingSystem.current().isWindows()) {
+            executable file("third_party/kotlin/kotlinc/bin/kotlinc.bat")
+        } else {
+            executable file("third_party/kotlin/kotlinc/bin/kotlinc");
+        }
+        args "-include-runtime"
+        args "-d"
+        args "build/test/${kotlinHostJar}"
+        args fileTree(dir: kotlinResourcesDir, include: '**/*.kt')
+    }
     dependsOn downloadDeps
     dependsOn jar_debugTestResources
     dependsOn jar_debugTestResourcesJava8
+    dependsOn jar_debugTestResourcesKotlin
 }
 
+// Proto lite generated code yields warnings when compiling with javac.
+// We change the options passed to javac to ignore it.
+compileExamplesJava.options.compilerArgs = ["-Xlint:none"]
+
 task buildExampleJars {
     dependsOn downloadProguard
     def examplesDir = file("src/test/examples")
@@ -922,6 +948,9 @@
         println "JCTF: compiling only"
         systemProperty 'jctf_compile_only', '1'
     }
+    if (project.hasProperty('test_dir')) {
+        systemProperty 'test_dir', project.property('test_dir')
+    }
 
     if (OperatingSystem.current().isLinux()
             || OperatingSystem.current().isMacOsX()
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 430624b..debe8a1 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -95,6 +95,9 @@
     private CompilationMode mode;
     private int minApiLevel = Constants.DEFAULT_ANDROID_API;
 
+    // Internal flag used by CompatDx to ignore dex files in archives.
+    protected boolean ignoreDexInArchive = false;
+
     protected Builder(CompilationMode mode) {
       this(AndroidApp.builder(), mode);
     }
@@ -108,6 +111,7 @@
       assert mode != null;
       this.app = builder;
       this.mode = mode;
+      app.setIgnoreDexInArchive(ignoreDexInArchive);
     }
 
     abstract B self();
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 64a1a0e..cb8c170 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -36,7 +36,7 @@
 
     private boolean intermediate = false;
 
-    private Builder() {
+    protected Builder() {
       super(CompilationMode.DEBUG);
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index f30ee0a..da90e82 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -434,7 +434,7 @@
             options.proguardConfiguration.getPrintMappingFile(),
             System.out,
             StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-        outputApp.writeProguardMap(closer, mapOut);
+        outputApp.writeProguardMap(mapOut);
       }
     }
     if (options.proguardConfiguration.isPrintSeeds()) {
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 6750f4c..9bcf510 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -450,7 +450,7 @@
     ExecutorService executor = ThreadUtils.getExecutorService(numberOfThreads);
     D8Output result;
     try {
-      D8Command.Builder builder = D8Command.builder();
+      D8Command.Builder builder = new CompatDxCommandBuilder();
       builder
           .addProgramFiles(inputs)
           .setMode(mode)
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
new file mode 100644
index 0000000..ba9daec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.compatdx;
+
+import com.android.tools.r8.D8Command;
+
+public class CompatDxCommandBuilder extends D8Command.Builder {
+  CompatDxCommandBuilder() {
+    ignoreDexInArchive = true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 2553650..cca3520 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -75,7 +75,7 @@
       throws IOException, ExecutionException {
     timing.begin("DexApplication.read");
     final DexApplication.Builder builder = new DexApplication.Builder(itemFactory, timing);
-    try (Closer closer = Closer.create()) {
+    try {
       List<Future<?>> futures = new ArrayList<>();
       // Still preload some of the classes, primarily for two reasons:
       // (a) class lazy loading is not supported for DEX files
@@ -84,9 +84,9 @@
       // (b) some of the class file resources don't provide information
       //     about class descriptor.
       // TODO: try and preload less classes.
-      readProguardMap(builder, executorService, futures, closer);
-      readMainDexList(builder, executorService, futures, closer);
-      ClassReader classReader = new ClassReader(executorService, futures, closer);
+      readProguardMap(builder, executorService, futures);
+      readMainDexList(builder, executorService, futures);
+      ClassReader classReader = new ClassReader(executorService, futures);
       classReader.readSources();
       ThreadUtils.awaitFutures(futures);
       classReader.initializeLazyClassCollection(builder);
@@ -130,13 +130,12 @@
   }
 
   private void readProguardMap(DexApplication.Builder builder, ExecutorService executorService,
-      List<Future<?>> futures, Closer closer)
+      List<Future<?>> futures)
       throws IOException {
     // Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
     if (inputApp.hasProguardMap()) {
       futures.add(executorService.submit(() -> {
-        try {
-          InputStream map = inputApp.getProguardMap(closer);
+        try (InputStream map = inputApp.getProguardMap()) {
           builder.setProguardMap(ProguardMapReader.mapperFromInputStream(map));
         } catch (IOException e) {
           throw new RuntimeException(e);
@@ -146,18 +145,18 @@
   }
 
   private void readMainDexList(DexApplication.Builder builder, ExecutorService executorService,
-      List<Future<?>> futures, Closer closer)
+      List<Future<?>> futures)
       throws IOException {
     if (inputApp.hasMainDexList()) {
       futures.add(executorService.submit(() -> {
-        try {
-          for (Resource resource : inputApp.getMainDexListResources()) {
-            InputStream input = closer.register(resource.getStream());
+        for (Resource resource : inputApp.getMainDexListResources()) {
+          try (InputStream input = resource.getStream()) {
             builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+          } catch (IOException e) {
+            throw new RuntimeException(e);
           }
-        } catch (IOException e) {
-          throw new RuntimeException(e);
         }
+
         builder.addToMainDexList(
             inputApp.getMainDexClasses()
                 .stream()
@@ -170,7 +169,6 @@
   private final class ClassReader {
     private final ExecutorService executorService;
     private final List<Future<?>> futures;
-    private final Closer closer;
 
     // We use concurrent queues to collect classes
     // since the classes can be collected concurrently.
@@ -180,10 +178,9 @@
     // Jar application reader to share across all class readers.
     private final JarApplicationReader application = new JarApplicationReader(options);
 
-    ClassReader(ExecutorService executorService, List<Future<?>> futures, Closer closer) {
+    ClassReader(ExecutorService executorService, List<Future<?>> futures) {
       this.executorService = executorService;
       this.futures = futures;
-      this.closer = closer;
     }
 
     private <T extends DexClass> void readDexSources(List<Resource> dexSources,
@@ -192,9 +189,11 @@
         List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
         int computedMinApiLevel = options.minApiLevel;
         for (Resource input : dexSources) {
-          DexFile file = new DexFile(closer.register(input.getStream()));
-          computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
-          fileReaders.add(new DexFileReader(file, classKind, itemFactory));
+          try (InputStream is = input.getStream()) {
+            DexFile file = new DexFile(is);
+            computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
+            fileReaders.add(new DexFileReader(file, classKind, itemFactory));
+          }
         }
         options.minApiLevel = computedMinApiLevel;
         for (DexFileReader reader : fileReaders) {
@@ -215,8 +214,16 @@
         ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
       JarClassFileReader reader = new JarClassFileReader(
           application, classKind.bridgeConsumer(classes::add));
+      // Read classes in parallel.
       for (Resource input : classSources) {
-        reader.read(DEFAULT_DEX_FILENAME, classKind, closer.register(input.getStream()));
+        futures.add(executorService.submit(() -> {
+          try (InputStream is = input.getStream()) {
+            reader.read(DEFAULT_DEX_FILENAME, classKind, is);
+          }
+          // No other way to have a void callable, but we want the IOException from the previous
+          // line to be wrapped into an ExecutionException.
+          return null;
+        }));
       }
     }
 
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 13a4d9d..80dea00 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -24,17 +24,19 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
 public class DexItemFactory {
 
-  private final Map<DexString, DexString> strings = new HashMap<>();
-  private final Map<DexString, DexType> types = new HashMap<>();
-  private final Map<DexField, DexField> fields = new HashMap<>();
-  private final Map<DexProto, DexProto> protos = new HashMap<>();
-  private final Map<DexMethod, DexMethod> methods = new HashMap<>();
-  private final Map<DexCallSite, DexCallSite> callSites = new HashMap<>();
-  private final Map<DexMethodHandle, DexMethodHandle> methodHandles = new HashMap<>();
+  private final ConcurrentHashMap<DexString, DexString> strings = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexString, DexType> types = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexField, DexField> fields = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexProto, DexProto> protos = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexMethod, DexMethod> methods = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexCallSite, DexCallSite> callSites = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexMethodHandle, DexMethodHandle> methodHandles =
+      new ConcurrentHashMap<>();
 
   // DexDebugEvent Canonicalization.
   private final Int2ObjectMap<AdvanceLine> advanceLines = new Int2ObjectOpenHashMap<>();
@@ -280,7 +282,7 @@
     }
   }
 
-  synchronized private static <T extends DexItem> T canonicalize(Map<T, T> map, T item) {
+  private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
     assert !internalSentinels.contains(item);
     T previous = map.putIfAbsent(item, item);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index a6b1f2c..2a3e431 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -339,12 +339,19 @@
   }
 
   private Local setLocalInfo(int index, Type type, DebugLocalInfo info) {
-    return setLocalInfoForRegister(getLocalRegister(index, type), type, info);
+    return setLocalInfoForRegister(getLocalRegister(index, type), info);
   }
 
-  private Local setLocalInfoForRegister(int register, Type type, DebugLocalInfo info) {
+  private Local setLocalInfoForRegister(int register, DebugLocalInfo info) {
     Local existingLocal = getLocalForRegister(register);
-    Local local = new Local(existingLocal.slot, info);
+    // TODO(ager, zerny): Kotlin debug information contains locals that are not referenced.
+    // That seems broken and we currently do not retain that debug information because
+    // we do not let locals debug information influence code generation. Debug information can
+    // be completely malformed, so we shouldn't let it influence code generation. However, we
+    // need to deal with these unused locals in the debug information. For now we
+    // use a null type for the slot, but we should reconsider that.
+    Slot slot = existingLocal != null ? existingLocal.slot : new Slot(register, null);
+    Local local = new Local(slot, info);
     locals[register] = local;
     return local;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 32342fc..c1e8515 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.StringUtils;
@@ -39,15 +40,16 @@
   private final AppInfoWithLiveness appInfo;
   private final RootSet rootSet;
   private final PackageObfuscationMode packageObfuscationMode;
+  private final Set<String> usedPackagePrefixes = Sets.newHashSet();
   private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
 
   private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
-  private final Map<String, ClassNamingState> states = new HashMap<>();
+  private final Map<String, Namespace> states = new HashMap<>();
   private final ImmutableList<String> packageDictionary;
   private final ImmutableList<String> classDictionary;
   private final boolean keepInnerClassStructure;
 
-  private final ClassNamingState topLevelState;
+  private final Namespace topLevelState;
 
   private GenericSignatureRewriter genericSignatureRewriter = new GenericSignatureRewriter();
 
@@ -66,8 +68,8 @@
     this.keepInnerClassStructure = options.attributeRemoval.signature;
 
     // Initialize top-level naming state.
-    topLevelState = new ClassNamingState(getPackageBinaryNameFromJavaType(
-        options.proguardConfiguration.getPackagePrefix()));
+    topLevelState = new Namespace(
+        getPackageBinaryNameFromJavaType(options.proguardConfiguration.getPackagePrefix()));
     states.computeIfAbsent("", k -> topLevelState);
   }
 
@@ -144,6 +146,8 @@
    */
   private void registerClassAsUsed(DexType type) {
     renaming.put(type, type.descriptor);
+    registerPackagePrefixesAsUsed(
+        getParentPackagePrefix(getClassBinaryNameFromDescriptor(type.descriptor.toSourceString())));
     usedTypeNames.add(type.descriptor);
     if (keepInnerClassStructure) {
       DexType outerClass = getOutClassForType(type);
@@ -156,6 +160,17 @@
     }
   }
 
+  /**
+   * Registers the given package prefix and all of parent packages as used.
+   */
+  private void registerPackagePrefixesAsUsed(String packagePrefix) {
+    String usedPrefix = packagePrefix;
+    while (usedPrefix.length() > 0) {
+      usedPackagePrefixes.add(usedPrefix);
+      usedPrefix = getParentPackagePrefix(usedPrefix);
+    }
+  }
+
   private DexType getOutClassForType(DexType type) {
     DexClass clazz = appInfo.definitionFor(type);
     if (clazz == null) {
@@ -176,7 +191,7 @@
   }
 
   private DexString computeName(DexClass clazz) {
-    ClassNamingState state = null;
+    Namespace state = null;
     if (keepInnerClassStructure) {
       // When keeping the nesting structure of inner classes, we have to insert the name
       // of the outer class for the $ prefix.
@@ -186,22 +201,22 @@
       }
     }
     if (state == null) {
-      state = getStateFor(clazz);
+      state = getStateForClass(clazz);
     }
     return state.nextTypeName();
   }
 
-  private ClassNamingState getStateFor(DexClass clazz) {
+  private Namespace getStateForClass(DexClass clazz) {
     String packageName = getPackageBinaryNameFromJavaType(clazz.type.getPackageDescriptor());
     // Check whether the given class should be kept.
     if (rootSet.keepPackageName.contains(clazz)) {
-      return states.computeIfAbsent(packageName, ClassNamingState::new);
+      return states.computeIfAbsent(packageName, Namespace::new);
     }
-    ClassNamingState state = topLevelState;
+    Namespace state = topLevelState;
     switch (packageObfuscationMode) {
       case NONE:
-        // TODO(b/36799686): general obfuscation.
-        state = states.computeIfAbsent(packageName, ClassNamingState::new);
+        // For general obfuscation, rename all the involved package prefixes.
+        state = getStateForPackagePrefix(packageName);
         break;
       case REPACKAGE:
         // For repackaging, all classes are repackaged to a single package.
@@ -210,16 +225,29 @@
       case FLATTEN:
         // For flattening, all packages are repackaged to a single package.
         state = states.computeIfAbsent(packageName, k -> {
-          String renamedPackageName =
-              getClassBinaryNameFromDescriptor(topLevelState.nextSuggestedName());
-          return new ClassNamingState(renamedPackageName);
+          String renamedPackagePrefix = topLevelState.nextPackagePrefix();
+          return new Namespace(renamedPackagePrefix);
         });
         break;
     }
     return state;
   }
 
-  private ClassNamingState getStateForOuterClass(DexType outer) {
+  private Namespace getStateForPackagePrefix(String prefix) {
+    return states.computeIfAbsent(prefix, k -> {
+      // Calculate the parent package prefix, e.g., La/b/c -> La/b
+      String parentPackage = getParentPackagePrefix(prefix);
+      // Create a state for parent package prefix, if necessary, in a recursive manner.
+      // That recursion should end when the parent package hits the top-level, "".
+      Namespace superState = getStateForPackagePrefix(parentPackage);
+      // From the super state, get a renamed package prefix for the current level.
+      String renamedPackagePrefix = superState.nextPackagePrefix();
+      // Create a new state, which corresponds to a new name space, for the current level.
+      return new Namespace(renamedPackagePrefix);
+    });
+  }
+
+  private Namespace getStateForOuterClass(DexType outer) {
     String prefix = getClassBinaryNameFromDescriptor(outer.toDescriptorString());
     return states.computeIfAbsent(prefix, k -> {
       // Create a naming state with this classes renaming as prefix.
@@ -235,7 +263,7 @@
         }
       }
       String binaryName = getClassBinaryNameFromDescriptor(renamed.toString());
-      return new ClassNamingState(binaryName, "$");
+      return new Namespace(binaryName, "$");
     });
   }
 
@@ -256,46 +284,67 @@
     }
   }
 
-  private class ClassNamingState {
+  private class Namespace {
 
     private final char[] packagePrefix;
     private int typeCounter = 1;
-    private Iterator<String> dictionaryIterator;
+    private int packageCounter = 1;
+    private Iterator<String> packageDictionaryIterator;
+    private Iterator<String> classDictionaryIterator;
 
-    ClassNamingState(String packageName) {
+    Namespace(String packageName) {
       this(packageName, "/");
     }
 
-    ClassNamingState(String packageName, String separator) {
+    Namespace(String packageName, String separator) {
       this.packagePrefix = ("L" + packageName
           // L or La/b/ (or La/b/C$)
           + (packageName.isEmpty() ? "" : separator))
           .toCharArray();
-      // TODO(b/36799686): general obfuscation should use packageDictionary when renaming package.
-      this.dictionaryIterator = classDictionary.iterator();
+      this.packageDictionaryIterator = packageDictionary.iterator();
+      this.classDictionaryIterator = classDictionary.iterator();
     }
 
-    public char[] getPackagePrefix() {
-      return packagePrefix;
-    }
-
-    String nextSuggestedName() {
+    private String nextSuggestedNameForClass() {
       StringBuilder nextName = new StringBuilder();
-      if (dictionaryIterator.hasNext()) {
-        nextName.append(packagePrefix).append(dictionaryIterator.next()).append(';');
+      if (classDictionaryIterator.hasNext()) {
+        nextName.append(packagePrefix).append(classDictionaryIterator.next()).append(';');
         return nextName.toString();
       } else {
         return StringUtils.numberToIdentifier(packagePrefix, typeCounter++, true);
       }
     }
 
-    private DexString nextTypeName() {
+    DexString nextTypeName() {
       DexString candidate;
       do {
-        candidate = appInfo.dexItemFactory.createString(nextSuggestedName());
+        candidate = appInfo.dexItemFactory.createString(nextSuggestedNameForClass());
       } while (usedTypeNames.contains(candidate));
       return candidate;
     }
+
+    private String nextSuggestedNameForSubpackage() {
+      StringBuilder nextName = new StringBuilder();
+      // Note that the differences between this method and the other variant for class renaming are
+      // 1) this one uses the different dictionary and counter,
+      // 2) this one does not append ';' at the end, and
+      // 3) this one removes 'L' at the beginning to make the return value a binary form.
+      if (packageDictionaryIterator.hasNext()) {
+        nextName.append(packagePrefix).append(packageDictionaryIterator.next());
+      } else {
+        nextName.append(StringUtils.numberToIdentifier(packagePrefix, packageCounter++, false));
+      }
+      return nextName.toString().substring(1);
+    }
+
+    String nextPackagePrefix() {
+      String candidate;
+      do {
+        candidate = nextSuggestedNameForSubpackage();
+      } while (usedPackagePrefixes.contains(candidate));
+      return candidate;
+    }
+
   }
 
   private class GenericSignatureRewriter implements GenericSignatureAction<DexType> {
@@ -355,4 +404,18 @@
       // nothing to do
     }
   }
+
+  /**
+   * Compute parent package prefix from the given package prefix.
+   *
+   * @param packagePrefix i.e. "Ljava/lang"
+   * @return parent package prefix i.e. "Ljava"
+   */
+  static String getParentPackagePrefix(String packagePrefix) {
+    int i = packagePrefix.lastIndexOf(DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR);
+    if (i < 0) {
+      return "";
+    }
+    return packagePrefix.substring(0, i);
+  }
 }
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 2480417..97a3679 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -212,8 +212,8 @@
   /**
    * Get the input stream of the proguard-map resource if it exists.
    */
-  public InputStream getProguardMap(Closer closer) throws IOException {
-    return proguardMap == null ? null : closer.register(proguardMap.getStream());
+  public InputStream getProguardMap() throws IOException {
+    return proguardMap == null ? null : proguardMap.getStream();
   }
 
   /**
@@ -382,10 +382,11 @@
     }
   }
 
-  public void writeProguardMap(Closer closer, OutputStream out) throws IOException {
-    InputStream input = getProguardMap(closer);
-    assert input != null;
-    out.write(ByteStreams.toByteArray(input));
+  public void writeProguardMap(OutputStream out) throws IOException {
+    try (InputStream input = getProguardMap()) {
+      assert input != null;
+      out.write(ByteStreams.toByteArray(input));
+    }
   }
 
   public void writeProguardSeeds(Closer closer, OutputStream out) throws IOException {
@@ -422,6 +423,7 @@
     private List<Resource> mainDexListResources = new ArrayList<>();
     private List<String> mainDexListClasses = new ArrayList<>();
     private Resource mainDexListOutput;
+    private boolean ignoreDexInArchive = false;
 
     // See AndroidApp::builder().
     private Builder() {
@@ -670,6 +672,17 @@
     }
 
     /**
+     * Ignore dex resources in input archives.
+     *
+     * In some situations (e.g. AOSP framework build) the input archives include both class and
+     * dex resources. Setting this flag ignores the dex resources and reads the class resources
+     * only.
+     */
+    public void setIgnoreDexInArchive(boolean value) {
+      ignoreDexInArchive = value;
+    }
+
+    /**
      * Build final AndroidApp.
      */
     public AndroidApp build() {
@@ -696,7 +709,7 @@
       } else if (isClassFile(file)) {
         programResources.add(Resource.fromFile(Resource.Kind.CLASSFILE, file));
       } else if (isArchive(file)) {
-        programFileArchiveReaders.add(new ProgramFileArchiveReader(file));
+        programFileArchiveReaders.add(new ProgramFileArchiveReader(file, ignoreDexInArchive));
       } else {
         throw new CompilationError("Unsupported source file type for file: " + file);
       }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 814ffd1..8341921 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -25,11 +25,13 @@
 class ProgramFileArchiveReader {
 
   private final Path archive;
+  private boolean ignoreDexInArchive;
   private List<Resource> dexResources = null;
   private List<Resource> classResources = null;
 
-  ProgramFileArchiveReader(Path archive) {
+  ProgramFileArchiveReader(Path archive, boolean ignoreDexInArchive) {
     this.archive = archive;
+    this.ignoreDexInArchive = ignoreDexInArchive;
   }
 
   private void readArchive() throws IOException {
@@ -41,9 +43,11 @@
       while ((entry = stream.getNextEntry()) != null) {
         Path name = Paths.get(entry.getName());
         if (isDexFile(name)) {
-          Resource resource =
-              new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
-          dexResources.add(resource);
+          if (!ignoreDexInArchive) {
+            Resource resource =
+                new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
+            dexResources.add(resource);
+          }
         } else if (isClassFile(name)) {
           String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
           Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
diff --git a/src/test/debugTestResourcesKotlin/KotlinApp.kt b/src/test/debugTestResourcesKotlin/KotlinApp.kt
new file mode 100644
index 0000000..7c15337
--- /dev/null
+++ b/src/test/debugTestResourcesKotlin/KotlinApp.kt
@@ -0,0 +1,21 @@
+// Copyright (c) 2017, 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.
+
+class KotlinApp {
+    companion object {
+        @JvmStatic fun main(args: Array<String>) {
+            println("Hello world!")
+            val instance = KotlinApp()
+            instance.processObject(instance, instance::printObject)
+        }
+    }
+
+    fun processObject(obj: Any, func: (Any) -> Unit) {
+        func(obj)
+    }
+
+    fun printObject(obj: Any) {
+        println(obj)
+    }
+}
\ No newline at end of file
diff --git a/src/test/examples/naming044/keep-rules-005.txt b/src/test/examples/naming044/keep-rules-005.txt
new file mode 100644
index 0000000..5583ccf
--- /dev/null
+++ b/src/test/examples/naming044/keep-rules-005.txt
@@ -0,0 +1,9 @@
+# Copyright (c) 2017, 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.
+
+-allowaccessmodification
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/naming101/a/a.java b/src/test/examples/naming101/a/a.java
new file mode 100644
index 0000000..261bfc3
--- /dev/null
+++ b/src/test/examples/naming101/a/a.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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 naming101.a;
+
+public class a {
+  static int f = 6;
+}
diff --git a/src/test/examples/naming101/a/b/c.java b/src/test/examples/naming101/a/b/c.java
new file mode 100644
index 0000000..b399bda
--- /dev/null
+++ b/src/test/examples/naming101/a/b/c.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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 naming101.a.b;
+
+public class c {
+  static boolean flag = true;
+}
diff --git a/src/test/examples/naming101/a/c.java b/src/test/examples/naming101/a/c.java
new file mode 100644
index 0000000..fbf890b
--- /dev/null
+++ b/src/test/examples/naming101/a/c.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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 naming101.a;
+
+public class c {
+  public static int a() {
+    return a.f;
+  }
+}
diff --git a/src/test/examples/naming101/b/a.java b/src/test/examples/naming101/b/a.java
new file mode 100644
index 0000000..c7eaa83
--- /dev/null
+++ b/src/test/examples/naming101/b/a.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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 naming101.b;
+
+public class a {
+  static boolean flag = false;
+}
diff --git a/src/test/examples/naming101/b/b.java b/src/test/examples/naming101/b/b.java
new file mode 100644
index 0000000..db21bd3
--- /dev/null
+++ b/src/test/examples/naming101/b/b.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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 naming101.b;
+
+public class b {
+  static boolean a() {
+    return a.flag;
+  }
+}
diff --git a/src/test/examples/naming101/c.java b/src/test/examples/naming101/c.java
new file mode 100644
index 0000000..7477024
--- /dev/null
+++ b/src/test/examples/naming101/c.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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 naming101;
+
+public class c {
+  static int i = 1;
+}
diff --git a/src/test/examples/naming101/d.java b/src/test/examples/naming101/d.java
new file mode 100644
index 0000000..3bb8131
--- /dev/null
+++ b/src/test/examples/naming101/d.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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 naming101;
+
+public class d {
+  static int c() {
+    return c.i;
+  }
+}
diff --git a/src/test/examples/naming101/keep-rules-001.txt b/src/test/examples/naming101/keep-rules-001.txt
new file mode 100644
index 0000000..a191b54
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-001.txt
@@ -0,0 +1,11 @@
+# Copyright (c) 2017, 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.
+
+-allowaccessmodification
+
+-repackageclasses ''
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/naming101/keep-rules-002.txt b/src/test/examples/naming101/keep-rules-002.txt
new file mode 100644
index 0000000..bbdde22
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-002.txt
@@ -0,0 +1,15 @@
+# Copyright (c) 2017, 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.
+
+-allowaccessmodification
+
+-repackageclasses 'naming101.a'
+
+-keep public class **.a {
+  *;
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/naming101/keep-rules-003.txt b/src/test/examples/naming101/keep-rules-003.txt
new file mode 100644
index 0000000..10b2856
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-003.txt
@@ -0,0 +1,11 @@
+# Copyright (c) 2017, 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.
+
+-allowaccessmodification
+
+-flattenpackagehierarchy ''
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/naming101/keep-rules-004.txt b/src/test/examples/naming101/keep-rules-004.txt
new file mode 100644
index 0000000..a4f3634
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-004.txt
@@ -0,0 +1,15 @@
+# Copyright (c) 2017, 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.
+
+-allowaccessmodification
+
+-flattenpackagehierarchy 'naming101'
+
+-keep public class **.a {
+  *;
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/naming101/keep-rules-005.txt b/src/test/examples/naming101/keep-rules-005.txt
new file mode 100644
index 0000000..5583ccf
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-005.txt
@@ -0,0 +1,9 @@
+# Copyright (c) 2017, 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.
+
+-allowaccessmodification
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/shaking1/print-mapping.ref b/src/test/examples/shaking1/print-mapping.ref
index 8382304..b75dbd3 100644
--- a/src/test/examples/shaking1/print-mapping.ref
+++ b/src/test/examples/shaking1/print-mapping.ref
@@ -1,4 +1,4 @@
 shaking1.Shaking -> shaking1.Shaking:
-shaking1.Used -> shaking1.a:
+shaking1.Used -> a.a:
     java.lang.String name -> a
     java.lang.String method() -> a
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index fd4cdccb..eee820f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -314,10 +314,22 @@
     }
   }
 
+  static class RetainedTemporaryFolder extends TemporaryFolder {
+    RetainedTemporaryFolder(java.io.File parentFolder) {
+      super(parentFolder);
+    }
+    protected void after() {} // instead of remove, do nothing
+  }
+
   // For non-Linux platforms create the temporary directory in the repository root to simplify
   // running Art in a docker container
   public static TemporaryFolder getTemporaryFolderForTest() {
-    return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
+    String tmpDir = System.getProperty("test_dir");
+    if (tmpDir == null) {
+      return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
+    } else {
+      return new RetainedTemporaryFolder(new java.io.File(tmpDir));
+    }
   }
 
   public static String getArtBinary() {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 57a1ce8..6340728 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -3,15 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayDeque;
@@ -89,12 +92,15 @@
       .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources.jar");
   private static final Path DEBUGGEE_JAVA8_JAR = Paths
       .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources_java8.jar");
+  private static final Path DEBUGGEE_KOTLIN_JAR = Paths
+      .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources_kotlin.jar");
 
   @ClassRule
   public static TemporaryFolder temp = new TemporaryFolder();
   private static Path jdwpDexD8 = null;
   private static Path debuggeeDexD8 = null;
   private static Path debuggeeJava8DexD8 = null;
+  private static Path debuggeeKotlinDexD8 = null;
 
   @Rule
   public TestName testName = new TestName();
@@ -103,43 +109,31 @@
   public static void setUp() throws Exception {
     // Convert jar to dex with d8 with debug info
     int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
-    {
-      Path dexOutputDir = temp.newFolder("d8-jdwp-jar").toPath();
-      jdwpDexD8 = dexOutputDir.resolve("classes.dex");
-      ToolHelper.runD8(
-          D8Command.builder()
-              .addProgramFiles(JDWP_JAR)
-              .setOutputPath(dexOutputDir)
-              .setMinApiLevel(minSdk)
-              .setMode(CompilationMode.DEBUG)
-              .build());
-    }
-    {
-      Path dexOutputDir = temp.newFolder("d8-debuggee-jar").toPath();
-      debuggeeDexD8 = dexOutputDir.resolve("classes.dex");
-      ToolHelper.runD8(
-          D8Command.builder()
-              .addProgramFiles(DEBUGGEE_JAR)
-              .setOutputPath(dexOutputDir)
-              .setMinApiLevel(minSdk)
-              .setMode(CompilationMode.DEBUG)
-              .build());
-    }
-    {
-      Path dexOutputDir = temp.newFolder("d8-debuggee-java8-jar").toPath();
-      debuggeeJava8DexD8 = dexOutputDir.resolve("classes.dex");
-      ToolHelper.runD8(
-          D8Command.builder()
-              .addProgramFiles(DEBUGGEE_JAVA8_JAR)
-              .setOutputPath(dexOutputDir)
-              .setMinApiLevel(minSdk)
-              .setMode(CompilationMode.DEBUG)
-              .build(),
-          options -> {
-            // Enable desugaring for preN runtimes
-            options.interfaceMethodDesugaring = OffOrAuto.Auto;
-          });
-    }
+    jdwpDexD8 = compileJarToDex(JDWP_JAR, minSdk, "d8-jdwp-jar", null);
+    debuggeeDexD8 = compileJarToDex(DEBUGGEE_JAR, minSdk, "d8-debuggee-jar", null);
+    debuggeeJava8DexD8 = compileJarToDex(DEBUGGEE_JAVA8_JAR, minSdk, "d8-debuggee-java8-jar",
+        options -> {
+          // Enable desugaring for preN runtimes
+          options.interfaceMethodDesugaring = OffOrAuto.Auto;
+        });
+    debuggeeKotlinDexD8 = compileJarToDex(DEBUGGEE_KOTLIN_JAR, minSdk, "d8-debuggee-kotlin-jar",
+        null);
+  }
+
+  private static Path compileJarToDex(Path jarToCompile, int minSdk, String tempDirName,
+      Consumer<InternalOptions> optionsConsumer) throws IOException, CompilationException {
+    assert jarToCompile.toFile().exists();
+    Path dexOutputDir = temp.newFolder(tempDirName).toPath();
+    ToolHelper.runD8(
+        D8Command.builder()
+            .addProgramFiles(jarToCompile)
+            .setOutputPath(dexOutputDir)
+            .setMinApiLevel(minSdk)
+            .setMode(CompilationMode.DEBUG)
+            .build(),
+        optionsConsumer);
+    return dexOutputDir.resolve("classes.dex");
+
   }
 
   protected final boolean supportsDefaultMethod() {
@@ -158,7 +152,7 @@
 
   protected final void runDebugTest(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
-    runDebugTest(false, debuggeeClass, commands);
+    runDebugTest(LanguageFeatures.JAVA_7, debuggeeClass, commands);
   }
 
   protected final void runDebugTestJava8(String debuggeeClass, JUnit3Wrapper.Command... commands)
@@ -168,12 +162,54 @@
 
   protected final void runDebugTestJava8(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
-    runDebugTest(true, debuggeeClass, commands);
+    runDebugTest(LanguageFeatures.JAVA_8, debuggeeClass, commands);
   }
 
-  private void runDebugTest(boolean useJava8, String debuggeeClass,
-      List<JUnit3Wrapper.Command> commands)
+  protected final void runDebugTestKotlin(String debuggeeClass, JUnit3Wrapper.Command... commands)
       throws Throwable {
+    runDebugTestKotlin(debuggeeClass, Arrays.asList(commands));
+  }
+
+  protected final void runDebugTestKotlin(String debuggeeClass,
+      List<JUnit3Wrapper.Command> commands) throws Throwable {
+    runDebugTest(LanguageFeatures.KOTLIN, debuggeeClass, commands);
+  }
+
+  protected enum LanguageFeatures {
+    JAVA_7(DEBUGGEE_JAR) {
+      @Override
+      public Path getDexPath() {
+        return debuggeeDexD8;
+      }
+    },
+    JAVA_8(DEBUGGEE_JAVA8_JAR) {
+      @Override
+      public Path getDexPath() {
+        return debuggeeJava8DexD8;
+      }
+    },
+    KOTLIN(DEBUGGEE_KOTLIN_JAR) {
+      @Override
+      public Path getDexPath() {
+        return debuggeeKotlinDexD8;
+      }
+    };
+
+    private final Path jarPath;
+
+    LanguageFeatures(Path jarPath) {
+      this.jarPath = jarPath;
+    }
+
+    public Path getJarPath() {
+      return jarPath;
+    }
+
+    public abstract Path getDexPath();
+  }
+
+  private void runDebugTest(LanguageFeatures languageFeatures, String debuggeeClass,
+      List<JUnit3Wrapper.Command> commands) throws Throwable {
     // Skip test due to unsupported runtime.
     Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
         ToolHelper.artSupported());
@@ -183,15 +219,9 @@
 
     String[] paths;
     if (RUNTIME_KIND == RuntimeKind.JAVA) {
-      paths = new String[] {
-          JDWP_JAR.toString(),
-          useJava8 ? DEBUGGEE_JAVA8_JAR.toString() : DEBUGGEE_JAR.toString()
-      };
+      paths = new String[] { JDWP_JAR.toString(), languageFeatures.getJarPath().toString() };
     } else {
-      paths = new String[] {
-          jdwpDexD8.toString(),
-          useJava8 ? debuggeeJava8DexD8.toString() : debuggeeDexD8.toString()
-      };
+      paths = new String[] { jdwpDexD8.toString(), languageFeatures.getDexPath().toString() };
     }
     new JUnit3Wrapper(debuggeeClass, paths, commands).runBare();
   }
@@ -580,6 +610,7 @@
         }
 
         public String getSourceFile() {
+          // TODO(shertz) support JSR-45
           Location location = getLocation();
           CommandPacket sourceFileCommand = new CommandPacket(
               JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinTest.java b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
new file mode 100644
index 0000000..1700964
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2017, 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.debug;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class KotlinTest extends DebugTestBase {
+
+  @Test
+  public void testKotlinApp() throws Throwable {
+    runDebugTestKotlin("KotlinApp",
+        breakpoint("KotlinApp$Companion", "main"),
+        run(),
+        inspect(s -> {
+          Assert.assertEquals("KotlinApp.kt", s.getSourceFile());
+          Assert.assertEquals(8, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+        }),
+        stepOver(),
+        inspect(s -> {
+          Assert.assertEquals(9, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+        }),
+        stepOver(),
+        inspect(s -> {
+          Assert.assertEquals(10, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          s.checkLocal("instance");
+        }),
+        run());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 1812fe1..671698b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.fail;
 
@@ -10,6 +11,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,13 +36,17 @@
     String artResult = null;
     try {
       artResult = runOnArt(builder, main);
-    } catch (CompilationError t) {
-      // Ignore.
+      fail();
+    } catch (ExecutionException t) {
+      if (!(t.getCause() instanceof CompilationError)) {
+        t.printStackTrace(System.out);
+        fail("Invalid dex class names should be compilation errors.");
+      }
     } catch (Throwable t) {
       t.printStackTrace(System.out);
       fail("Invalid dex class names should be compilation errors.");
     }
-    assert artResult == null : "Invalid dex class names should be rejected.";
+    assertNull("Invalid dex class names should be rejected.", artResult);
   }
 
   @Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 791ecca..87d350c 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,11 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.fail;
 
 import com.android.tools.r8.errors.CompilationError;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,10 +35,14 @@
     String artResult = null;
     try {
       artResult = runOnArt(builder, main);
-    } catch (CompilationError t) {
-      // Ignore.
+      fail();
+    } catch (ExecutionException t) {
+      if (!(t.getCause() instanceof CompilationError)) {
+        t.printStackTrace(System.out);
+        fail("Invalid dex field names should be compilation errors.");
+      }
     }
-    assert artResult == null : "Invalid dex class names should be rejected.";
+    assertNull("Invalid dex field names should be rejected.", artResult);
   }
 
   @Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index f8b7615..e1ace71 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,13 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.fail;
 
+import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,13 +37,17 @@
     String artResult = null;
     try {
       artResult = runOnArt(builder, main);
-    } catch (CompilationError t) {
-      // Ignore.
+      fail();
+    } catch (ExecutionException t) {
+      if (!(t.getCause() instanceof CompilationError)) {
+        t.printStackTrace(System.out);
+        fail("Invalid dex method names should be compilation errors.");
+      }
     } catch (Throwable t) {
       t.printStackTrace(System.out);
-      fail("Invalid dex class names should be compilation errors.");
+      fail("Invalid dex method names should be compilation errors.");
     }
-    assert artResult == null : "Invalid dex class names should be rejected.";
+    assertNull("Invalid dex method names should be rejected.", artResult);
   }
 
   @Parameters
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 66137df..d5f9c6a 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -70,10 +70,8 @@
                 .addProguardConfigurationFiles(keepRulesPath)
                 .build());
     if (androidApp.hasProguardMap()) {
-      try (Closer closer = Closer.create()) {
-        androidApp.writeProguardMap(closer, new FileOutputStream(
+        androidApp.writeProguardMap(new FileOutputStream(
             Paths.get(tmpOutputDir.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME).toFile()));
-      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index feaf0d9..b8e22d2 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.naming.ClassNameMinifier.getParentPackagePrefix;
 import static com.android.tools.r8.utils.DescriptorUtils.getPackageNameFromDescriptor;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.CharMatcher;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collection;
@@ -45,13 +47,19 @@
 
   @Parameters(name = "test: {0} keep: {1}")
   public static Collection<Object[]> data() {
-    List<String> tests = Arrays.asList("naming044");
+    List<String> tests = Arrays.asList("naming044", "naming101");
 
     Map<String, BiConsumer<DexItemFactory, NamingLens>> inspections = new HashMap<>();
     inspections.put("naming044:keep-rules-001.txt", PackageNamingTest::test044_rule001);
     inspections.put("naming044:keep-rules-002.txt", PackageNamingTest::test044_rule002);
     inspections.put("naming044:keep-rules-003.txt", PackageNamingTest::test044_rule003);
     inspections.put("naming044:keep-rules-004.txt", PackageNamingTest::test044_rule004);
+    inspections.put("naming044:keep-rules-005.txt", PackageNamingTest::test044_rule005);
+    inspections.put("naming101:keep-rules-001.txt", PackageNamingTest::test101_rule001);
+    inspections.put("naming101:keep-rules-002.txt", PackageNamingTest::test101_rule002);
+    inspections.put("naming101:keep-rules-003.txt", PackageNamingTest::test101_rule003);
+    inspections.put("naming101:keep-rules-004.txt", PackageNamingTest::test101_rule004);
+    inspections.put("naming101:keep-rules-005.txt", PackageNamingTest::test101_rule005);
 
     return createTests(tests, inspections);
   }
@@ -135,4 +143,111 @@
         getPackageNameFromDescriptor(naming.lookupDescriptor(sub).toSourceString()),
         getPackageNameFromDescriptor(naming.lookupDescriptor(b).toSourceString()));
   }
+
+  private static void test044_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+    // All packages are renamed somehow. Need to check package hierarchy is consistent.
+    DexType a = dexItemFactory.createType("Lnaming044/A;");
+    assertEquals(1, countPackageDepth(naming.lookupDescriptor(a).toSourceString()));
+    DexType b = dexItemFactory.createType("Lnaming044/B;");
+    assertEquals(1, countPackageDepth(naming.lookupDescriptor(b).toSourceString()));
+    assertEquals(
+        getPackageNameFromDescriptor(naming.lookupDescriptor(a).toSourceString()),
+        getPackageNameFromDescriptor(naming.lookupDescriptor(b).toSourceString()));
+
+    DexType sub_a = dexItemFactory.createType("Lnaming044/sub/SubA;");
+    assertEquals(2, countPackageDepth(naming.lookupDescriptor(sub_a).toSourceString()));
+    DexType sub_b = dexItemFactory.createType("Lnaming044/sub/SubB;");
+    assertEquals(2, countPackageDepth(naming.lookupDescriptor(sub_b).toSourceString()));
+    assertEquals(
+        getPackageNameFromDescriptor(naming.lookupDescriptor(sub_a).toSourceString()),
+        getPackageNameFromDescriptor(naming.lookupDescriptor(sub_b).toSourceString()));
+
+    // Lnaming044/B -> La/c --prefix--> La
+    // Lnaming044/sub/SubB -> La/b/b --prefix--> La/b --prefix--> La
+    assertEquals(
+        getParentPackagePrefix(naming.lookupDescriptor(b).toSourceString()),
+        getParentPackagePrefix(
+            getParentPackagePrefix(naming.lookupDescriptor(sub_b).toSourceString())));
+  }
+
+  private static void test101_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
+    // All classes are moved to the top-level package, hence no package separator.
+    DexType c = dexItemFactory.createType("Lnaming101/c;");
+    assertFalse(naming.lookupDescriptor(c).toSourceString().contains("/"));
+
+    DexType abc = dexItemFactory.createType("Lnaming101/a/b/c;");
+    assertFalse(naming.lookupDescriptor(abc).toSourceString().contains("/"));
+    assertNotEquals(
+        naming.lookupDescriptor(abc).toSourceString(),
+        naming.lookupDescriptor(c).toSourceString());
+  }
+
+  private static void test101_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
+    // Check naming101.a.a is kept due to **.a
+    DexType a = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+    // Repackaged to naming101.a, but naming101.a.a exists to make a name conflict.
+    // Thus, everything else should not be renamed to 'a',
+    // except for naming101.b.a, which is also kept due to **.a
+    List<String> klasses = ImmutableList.of(
+        "Lnaming101/c;",
+        "Lnaming101/d;",
+        "Lnaming101/a/c;",
+        "Lnaming101/a/b/c;",
+        "Lnaming101/b/b;");
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String renamedName = naming.lookupDescriptor(k).toSourceString();
+      assertEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+      assertNotEquals("Lnaming101/a/a;", renamedName);
+    }
+  }
+
+  private static void test101_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
+    // All packages are moved to the top-level package, hence only one package separator.
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals(1, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
+
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals(1, countPackageDepth(naming.lookupDescriptor(ba).toSourceString()));
+
+    assertNotEquals(
+        getPackageNameFromDescriptor(naming.lookupDescriptor(aa).toSourceString()),
+        getPackageNameFromDescriptor(naming.lookupDescriptor(ba).toSourceString()));
+  }
+
+  private static void test101_rule004(DexItemFactory dexItemFactory, NamingLens naming) {
+    // Check naming101.a.a is kept due to **.a
+    DexType a = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+    // Flattened to naming101, hence all other classes will be in naming101.* package.
+    // Due to naming101.a.a, prefix naming101.a is already used. So, any other classes,
+    // except for naming101.a.c, should not have naming101.a as package.
+    List<String> klasses = ImmutableList.of(
+        "Lnaming101/c;",
+        "Lnaming101/d;",
+        "Lnaming101/a/b/c;",
+        "Lnaming101/b/a;",
+        "Lnaming101/b/b;");
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String renamedName = naming.lookupDescriptor(k).toSourceString();
+      assertNotEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+    }
+  }
+
+  private static void test101_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+    // All packages are renamed somehow. Need to check package hierarchy is consistent.
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals(2, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
+    DexType abc = dexItemFactory.createType("Lnaming101/a/b/c;");
+    assertEquals(3, countPackageDepth(naming.lookupDescriptor(abc).toSourceString()));
+
+    // Lnaming101/a/a; -> La/a/a; --prefix--> La/a
+    // Lnaming101/a/b/c; -> La/a/a/a; --prefix--> La/a/a --prefix--> La/a
+    assertEquals(
+        getParentPackagePrefix(naming.lookupDescriptor(aa).toSourceString()),
+        getParentPackagePrefix(
+            getParentPackagePrefix(naming.lookupDescriptor(abc).toSourceString())));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java b/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
index 72b452f..1426a0f 100644
--- a/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
@@ -30,6 +30,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.io.Closer;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashMap;
@@ -115,8 +116,8 @@
         ToolHelper.runR8(command, (options) -> options.skipDebugLineNumberOpt = !compressRanges);
 
     ClassNameMapper classNameMapper;
-    try (Closer closer = Closer.create()) {
-      classNameMapper = ProguardMapReader.mapperFromInputStream(result.getProguardMap(closer));
+    try (InputStream is = result.getProguardMap()) {
+      classNameMapper = ProguardMapReader.mapperFromInputStream(is);
     }
     if (compressRanges) {
       classNameMapper.forAllClassNamings(this::ensureRangesAreUniquePerClass);
diff --git a/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java b/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java
index 034a288..58180e3 100644
--- a/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java
@@ -44,6 +44,15 @@
   }
 
   @Test
+  public void fromDescriptor() throws IOException {
+    String obj = "Ljava/lang/Object;";
+    assertEquals("Object", DescriptorUtils.getSimpleClassNameFromDescriptor(obj));
+    assertEquals("java.lang.Object", DescriptorUtils.getClassNameFromDescriptor(obj));
+    assertEquals("java.lang", DescriptorUtils.getPackageNameFromDescriptor(obj));
+    assertEquals("java/lang/Object", DescriptorUtils.getClassBinaryNameFromDescriptor(obj));
+  }
+
+  @Test
   public void toJavaType() throws IOException {
     assertEquals("boolean", DescriptorUtils.descriptorToJavaType("Z"));
     assertEquals("byte", DescriptorUtils.descriptorToJavaType("B"));
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
new file mode 100644
index 0000000..942c691
--- /dev/null
+++ b/third_party/kotlin.tar.gz.sha1
@@ -0,0 +1 @@
+21b6244cb0a5bca1f1d046d00540a610c02cd714
\ No newline at end of file
diff --git a/tools/test.py b/tools/test.py
index ccc36d8..1da077b 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -44,7 +44,8 @@
       help='Print a line before a tests starts and after it ends to stdout.',
       default=False, action='store_true')
   result.add_option('--tool',
-      help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if "--all_tests" enabled.',
+      help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if'
+          ' "--all_tests" enabled.',
       default=None, choices=["r8", "d8"])
   result.add_option('--jctf',
       help='Run JCTF tests with: "r8" (default) or "d8".',
@@ -56,11 +57,15 @@
       help="Don't run, only compile JCTF tests.",
       default=False, action='store_true')
   result.add_option('--disable_assertions',
-      help="Disable assertions when running tests.",
+      help='Disable assertions when running tests.',
       default=False, action='store_true')
   result.add_option('--with_code_coverage',
-      help="Enable code coverage with Jacoco.",
+      help='Enable code coverage with Jacoco.',
       default=False, action='store_true')
+  result.add_option('--test_dir',
+      help='Use a custom directory for the test artifacts instead of a'
+          ' temporary (which is automatically removed after the test).'
+          ' Note that the directory will not be cleared before the test.')
 
   return result.parse_args()
 
@@ -112,6 +117,10 @@
     gradle_args.append('jctfCommonJar')
     gradle_args.append('-x')
     gradle_args.append('jctfTestsClasses')
+  if options.test_dir:
+    gradle_args.append('-Ptest_dir=' + options.test_dir)
+    if not os.path.exists(options.test_dir):
+      os.makedirs(options.test_dir)
 
   # Add Gradle tasks
   gradle_args.append('cleanTest')