Merge "Remove use of Hashtable"
diff --git a/build.gradle b/build.gradle
index 35da63e..ef09f76 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,6 +10,7 @@
 apply plugin: 'com.google.protobuf'
 apply plugin: 'com.cookpad.android.licensetools'
 apply plugin: 'net.ltgt.errorprone-base'
+apply plugin: "net.ltgt.apt"
 
 def errorProneConfiguration = [
     '-XepDisableAllChecks',
@@ -51,6 +52,7 @@
         // classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
         classpath files("third_party/shadow/shadow-2.0.1.jar")
         classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+        classpath "net.ltgt.gradle:gradle-apt-plugin:0.12"
     }
 }
 
@@ -169,12 +171,16 @@
     jctfTestsCompile sourceSets.jctfCommon.output
     examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '6.0'
     examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: '6.0'
+    // Import Guava for @Nullable annotation
+    examplesCompile 'com.google.guava:guava:23.0'
     examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0'
+    examplesCompileOnly "com.google.auto.value:auto-value:1.5"
     examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0'
     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.4-3'
+    apt 'com.google.auto.value:auto-value:1.5'
 }
 
 licenseTools {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ae6326e..5255fc5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -30,8 +30,7 @@
 import com.android.tools.r8.shaking.DiscardedChecker;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.MainDexListBuilder;
-import com.android.tools.r8.shaking.ProguardClassNameList;
-import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
+import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.shaking.ReasonPrinter;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -147,15 +146,9 @@
   }
 
   private Set<DexType> filterMissingClasses(Set<DexType> missingClasses,
-      ProguardClassNameList dontWarnPatterns) {
+      ProguardClassFilter dontWarnPatterns) {
     Set<DexType> result = new HashSet<>(missingClasses);
-    dontWarnPatterns.forEachTypeMatcher(matcher -> {
-      if (matcher instanceof MatchSpecificType) {
-        result.remove(((MatchSpecificType) matcher).type);
-      } else {
-        result.removeIf(matcher::matches);
-      }
-    });
+    dontWarnPatterns.filterOutMatches(result);
     return result;
   }
 
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index d2fd60c..4c49a6a 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -50,6 +50,8 @@
       int minApi = 1;
       boolean forceProguardCompatibility = false;
       boolean multiDex = false;
+      boolean coreLibrary = false;
+
       ImmutableList.Builder<String> builder = ImmutableList.builder();
       if (args.length > 0) {
         StringBuilder currentLine = new StringBuilder(args[0]);
@@ -64,6 +66,8 @@
               output = args[++i];
             } else if (arg.equals("--multi-dex")) {
               multiDex = true;
+            } else if (arg.equals("--core-library")) {
+              coreLibrary = true;
             } else if (arg.equals("-outjars")) {
               throw new CompilationException(
                   "Proguard argument -outjar is not supported. Use R8 compatible --output flag");
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 41f9748..dccf887 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -58,7 +58,6 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.security.MessageDigest;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -69,7 +68,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.ToIntFunction;
 import java.util.zip.Adler32;
 
@@ -101,29 +99,13 @@
     }
     dest.putUleb128(mapping.getOffsetFor(annotation.type));
     dest.putUleb128(annotation.elements.length);
-    assert isSorted(annotation.elements, (element) -> element.name);
+    assert PresortedComparable.isSorted(annotation.elements, (element) -> element.name);
     for (DexAnnotationElement element : annotation.elements) {
       dest.putUleb128(mapping.getOffsetFor(element.name));
       element.value.writeTo(dest, mapping);
     }
   }
 
-  private static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
-    return isSorted(items, KeyedDexItem::getKey);
-  }
-
-  private static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
-    T current = null;
-    for (S item : items) {
-      T next = getter.apply(item);
-      if (current != null && current.compareTo(next) >= 0) {
-        return false;
-      }
-      current = next;
-    }
-    return true;
-  }
-
   public FileWriter collect() {
     // Use the class array from the mapping, as it has a deterministic iteration order.
     new ProgramClassDependencyCollector(application, mapping.getClasses())
@@ -518,7 +500,7 @@
 
   private void writeAnnotationSet(DexAnnotationSet set) {
     assert !set.isEmpty();
-    assert isSorted(set.annotations, (item) -> item.annotation.type);
+    assert PresortedComparable.isSorted(set.annotations, (item) -> item.annotation.type);
     mixedSectionOffsets.setOffsetFor(set, dest.align(4));
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Writing AnnotationSet @ 0x%08x.", dest.position());
@@ -564,7 +546,7 @@
   }
 
   private void writeEncodedFields(DexEncodedField[] fields) {
-    assert isSorted(fields);
+    assert PresortedComparable.isSorted(fields);
     int currentOffset = 0;
     for (DexEncodedField field : fields) {
       int nextOffset = mapping.getOffsetFor(field.field);
@@ -576,7 +558,7 @@
   }
 
   private void writeEncodedMethods(DexEncodedMethod[] methods, boolean clearBodies) {
-    assert isSorted(methods);
+    assert PresortedComparable.isSorted(methods);
     int currentOffset = 0;
     for (DexEncodedMethod method : methods) {
       int nextOffset = mapping.getOffsetFor(method.method);
@@ -614,21 +596,12 @@
   }
 
   private void addStaticFieldValues(DexProgramClass clazz) {
-    DexEncodedField[] fields = clazz.staticFields();
-    int length = 0;
-    List<DexValue> values = new ArrayList<>(fields.length);
-    for (int i = 0; i < fields.length; i++) {
-      DexEncodedField field = fields[i];
-      assert field.staticValue != null;
-      values.add(field.staticValue);
-      if (!field.staticValue.isDefault(field.field.type, application.dexItemFactory)) {
-        length = i + 1;
-      }
-    }
-    if (length > 0) {
-      DexEncodedArray staticValues = new DexEncodedArray(
-          values.subList(0, length).toArray(new DexValue[length]));
-      clazz.setStaticValues(staticValues);
+    clazz.computeStaticValues(application.dexItemFactory);
+    // We have collected the individual components of this array due to the data stored in
+    // DexEncodedField#staticValues. However, we have to collect the DexEncodedArray itself
+    // here.
+    DexEncodedArray staticValues = clazz.getStaticValues();
+    if (staticValues != null) {
       mixedSectionOffsets.add(staticValues);
     }
   }
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 8f3a923..5050711 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -23,9 +23,22 @@
   public DexType superType;
   public DexTypeList interfaces;
   public DexString sourceFile;
+
+  /**
+   * Access has to be synchronized during concurrent collection/writing phase.
+   */
   protected DexEncodedField[] staticFields;
+  /**
+   * Access has to be synchronized during concurrent collection/writing phase.
+   */
   protected DexEncodedField[] instanceFields;
+  /**
+   * Access has to be synchronized during concurrent collection/writing phase.
+   */
   protected DexEncodedMethod[] directMethods;
+  /**
+   * Access has to be synchronized during concurrent collection/writing phase.
+   */
   protected DexEncodedMethod[] virtualMethods;
   public DexAnnotationSet annotations;
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 3c9c796..b64053a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -8,17 +8,22 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.utils.ProgramResource;
 import com.android.tools.r8.utils.ProgramResource.Kind;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.function.Supplier;
 
 public class DexProgramClass extends DexClass implements Supplier<DexProgramClass> {
+
+  private static DexEncodedArray SENTINEL_NOT_YET_COMPUTED = new DexEncodedArray(new DexValue[0]);
+
   private final ProgramResource.Kind originKind;
-  private DexEncodedArray staticValues;
+  private DexEncodedArray staticValues = SENTINEL_NOT_YET_COMPUTED;
   private final Collection<DexProgramClass> synthesizedFrom;
 
   public DexProgramClass(
@@ -97,10 +102,17 @@
       if (interfaces != null) {
         interfaces.collectIndexedItems(indexedItems);
       }
-      collectAll(indexedItems, staticFields);
-      collectAll(indexedItems, instanceFields);
-      collectAll(indexedItems, directMethods);
-      collectAll(indexedItems, virtualMethods);
+      synchronizedCollectAll(indexedItems, staticFields);
+      synchronizedCollectAll(indexedItems, instanceFields);
+      synchronizedCollectAll(indexedItems, directMethods);
+      synchronizedCollectAll(indexedItems, virtualMethods);
+    }
+  }
+
+  private static <T extends DexItem> void synchronizedCollectAll(IndexedItemCollection collection,
+      T[] items) {
+    synchronized (items) {
+      collectAll(collection, items);
     }
   }
 
@@ -120,10 +132,10 @@
     // We only have a class data item if there are methods or fields.
     if (hasMethodsOrFields()) {
       collector.add(this);
-      collectAll(collector, directMethods);
-      collectAll(collector, virtualMethods);
-      collectAll(collector, staticFields);
-      collectAll(collector, instanceFields);
+      synchronizedCollectAll(collector, directMethods);
+      synchronizedCollectAll(collector, virtualMethods);
+      synchronizedCollectAll(collector, staticFields);
+      synchronizedCollectAll(collector, instanceFields);
     }
     if (annotations != null) {
       annotations.collectMixedSectionItems(collector);
@@ -134,6 +146,13 @@
     annotations.collectMixedSectionItems(collector);
   }
 
+  private static <T extends DexItem> void synchronizedCollectAll(MixedSectionCollection collection,
+      T[] items) {
+    synchronized (items) {
+      collectAll(collection, items);
+    }
+  }
+
   @Override
   public String toString() {
     return type.toString();
@@ -176,11 +195,15 @@
   }
 
   private boolean hasAnnotations(DexEncodedField[] fields) {
-    return fields != null && Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotation);
+    synchronized (fields) {
+      return Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotation);
+    }
   }
 
   private boolean hasAnnotations(DexEncodedMethod[] methods) {
-    return methods != null && Arrays.stream(methods).anyMatch(DexEncodedMethod::hasAnnotation);
+    synchronized (methods) {
+      return Arrays.stream(methods).anyMatch(DexEncodedMethod::hasAnnotation);
+    }
   }
 
   private static Collection<DexProgramClass> accumulateSynthesizedFrom(
@@ -196,11 +219,38 @@
     return accumulated;
   }
 
-  public void setStaticValues(DexEncodedArray staticValues) {
-    this.staticValues = staticValues;
+  public void computeStaticValues(DexItemFactory factory) {
+    // It does not actually hurt to compute this multiple times. So racing on staticValues is OK.
+    if (staticValues == SENTINEL_NOT_YET_COMPUTED) {
+      synchronized (staticFields) {
+        assert PresortedComparable.isSorted(staticFields);
+        DexEncodedField[] fields = staticFields;
+        int length = 0;
+        List<DexValue> values = new ArrayList<>(fields.length);
+        for (int i = 0; i < fields.length; i++) {
+          DexEncodedField field = fields[i];
+          assert field.staticValue != null;
+          values.add(field.staticValue);
+          if (!field.staticValue.isDefault(field.field.type, factory)) {
+            length = i + 1;
+          }
+        }
+        if (length > 0) {
+          staticValues = new DexEncodedArray(
+              values.subList(0, length).toArray(new DexValue[length]));
+        } else {
+          staticValues = null;
+        }
+      }
+    }
   }
 
   public DexEncodedArray getStaticValues() {
+    // The sentinel value is left over for classes that actually have no fields.
+    if (staticValues == SENTINEL_NOT_YET_COMPUTED) {
+      assert !hasMethodsOrFields();
+      return null;
+    }
     return staticValues;
   }
 
@@ -208,30 +258,22 @@
     assert !virtualMethod.accessFlags.isStatic();
     assert !virtualMethod.accessFlags.isPrivate();
     assert !virtualMethod.accessFlags.isConstructor();
-    virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
-    virtualMethods[virtualMethods.length - 1] = virtualMethod;
+    synchronized (virtualMethods) {
+      virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
+      virtualMethods[virtualMethods.length - 1] = virtualMethod;
+    }
   }
 
   public void addStaticMethod(DexEncodedMethod staticMethod) {
     assert staticMethod.accessFlags.isStatic();
     assert !staticMethod.accessFlags.isPrivate();
-    directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
-    directMethods[directMethods.length - 1] = staticMethod;
-  }
-
-  public void removeStaticMethod(DexEncodedMethod staticMethod) {
-    assert staticMethod.accessFlags.isStatic();
-    DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[directMethods.length - 1];
-    int toIndex = 0;
-    for (int fromIndex = 0; fromIndex < directMethods.length; fromIndex++) {
-      if (directMethods[fromIndex] != staticMethod) {
-        newDirectMethods[toIndex++] = directMethods[fromIndex];
-      }
+    synchronized (directMethods) {
+      directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
+      directMethods[directMethods.length - 1] = staticMethod;
     }
-    directMethods = newDirectMethods;
   }
 
-  public synchronized void sortMembers() {
+  public void sortMembers() {
     sortEncodedFields(staticFields);
     sortEncodedFields(instanceFields);
     sortEncodedMethods(directMethods);
@@ -239,11 +281,15 @@
   }
 
   private void sortEncodedFields(DexEncodedField[] fields) {
-    Arrays.sort(fields, Comparator.comparing(a -> a.field));
+    synchronized (fields) {
+      Arrays.sort(fields, Comparator.comparing(a -> a.field));
+    }
   }
 
   private void sortEncodedMethods(DexEncodedMethod[] methods) {
-    Arrays.sort(methods, Comparator.comparing(a -> a.method));
+    synchronized (methods) {
+      Arrays.sort(methods, Comparator.comparing(a -> a.method));
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index eb130d6..a4ee2ee 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -4,8 +4,26 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.NamingLens;
+import java.util.function.Function;
 
 public interface PresortedComparable<T> extends Presorted, Comparable<T> {
+
+  static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+    return isSorted(items, KeyedDexItem::getKey);
+  }
+
+  static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+    T current = null;
+    for (S item : items) {
+      T next = getter.apply(item);
+      if (current != null && current.compareTo(next) >= 0) {
+        return false;
+      }
+      current = next;
+    }
+    return true;
+  }
+
   // Slow comparison methods that make no use of indices for comparisons. These are used
   // for sorting operations when reading dex files.
   int slowCompareTo(T other);
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index fe0ff72..552516d 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -12,19 +12,19 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.Map;
 
 class IdentifierMinifier {
 
   private final AppInfo appInfo;
-  private final ProguardClassNameList adaptClassStrings;
+  private final ProguardClassFilter adaptClassStrings;
   private final NamingLens lens;
 
   IdentifierMinifier(
       AppInfo appInfo,
-      ProguardClassNameList adaptClassStrings,
+      ProguardClassFilter adaptClassStrings,
       NamingLens lens) {
     this.appInfo = appInfo;
     this.adaptClassStrings = adaptClassStrings;
@@ -32,7 +32,7 @@
   }
 
   void run() {
-    if (adaptClassStrings.size() != 0) {
+    if (!adaptClassStrings.isEmpty()) {
       handleAdaptClassStrings();
     }
     // TODO(b/36799092): Handle influx of string literals from call sites to annotated members.
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
new file mode 100644
index 0000000..939dca3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
@@ -0,0 +1,64 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
+import com.google.common.collect.ImmutableList;
+import java.util.Set;
+
+public class ProguardClassFilter {
+  private final ImmutableList<ProguardClassNameList> patterns;
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private final ImmutableList.Builder<ProguardClassNameList> patterns = ImmutableList.builder();
+
+    private Builder() {
+    }
+
+    public Builder addPattern(ProguardClassNameList pattern) {
+      patterns.add(pattern);
+      return this;
+    }
+
+    ProguardClassFilter build() {
+      return new ProguardClassFilter(patterns.build());
+    }
+  }
+
+  private ProguardClassFilter(ImmutableList<ProguardClassNameList> patterns) {
+    this.patterns = patterns;
+  }
+
+  public boolean isEmpty() {
+    return patterns.size() == 0;
+  }
+
+  public boolean matches(DexType type) {
+    for (ProguardClassNameList pattern : patterns) {
+      if (pattern.matches(type)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public void filterOutMatches(Set<DexType> types) {
+    for (ProguardClassNameList pattern : patterns) {
+      pattern.forEachTypeMatcher(matcher -> {
+        if (matcher instanceof MatchSpecificType) {
+          assert matcher.getSpecificType() != null;
+          types.remove(matcher.getSpecificType());
+        } else {
+          types.removeIf(matcher::matches);
+        }
+      });
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index dc800a3..3251c04 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -34,7 +34,7 @@
     private boolean verbose;
     private String renameSourceFileAttribute;
     private final List<String> keepAttributePatterns = new ArrayList<>();
-    private ProguardClassNameList dontWarnPatterns;
+    private ProguardClassFilter.Builder dontWarnPatterns = ProguardClassFilter.builder();
     protected final List<ProguardConfigurationRule> rules = new ArrayList<>();
     private final DexItemFactory dexItemFactory;
     private boolean printSeeds;
@@ -44,8 +44,7 @@
     private Path packageObfuscationDictionary;
     private boolean useUniqueClassMemberNames;
     private boolean keepParameterNames;
-    private ProguardClassNameList adaptClassStrings = ProguardClassNameList.emptyList();
-
+    private ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
     private Builder(DexItemFactory dexItemFactory) {
       this.dexItemFactory = dexItemFactory;
       resetProguardDefaults();
@@ -69,7 +68,7 @@
       verbose = false;
       renameSourceFileAttribute = null;
       keepAttributePatterns.clear();
-      dontWarnPatterns = ProguardClassNameList.emptyList();
+      dontWarnPatterns = ProguardClassFilter.builder();
       rules.clear();
       printSeeds = false;
       seedFile = null;
@@ -78,6 +77,7 @@
       packageObfuscationDictionary = null;
       useUniqueClassMemberNames = false;
       keepParameterNames = false;
+      adaptClassStrings = ProguardClassFilter.builder();
     }
 
     public void addInjars(List<FilteredClassPath> injars) {
@@ -162,8 +162,8 @@
       this.rules.add(rule);
     }
 
-    public void setDontWarnPatterns(ProguardClassNameList patterns) {
-      dontWarnPatterns = patterns;
+    public void addDontWarnPattern(ProguardClassNameList pattern) {
+      dontWarnPatterns.addPattern(pattern);
     }
 
     public void setSeedFile(Path seedFile) {
@@ -202,8 +202,8 @@
       return keepParameterNames;
     }
 
-    public void setAdaptClassStrings(ProguardClassNameList adaptClassStrings) {
-      this.adaptClassStrings = adaptClassStrings;
+    public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {
+      adaptClassStrings.addPattern(pattern);
     }
 
     public ProguardConfiguration build() throws CompilationException {
@@ -226,7 +226,7 @@
           verbose,
           renameSourceFileAttribute,
           keepAttributePatterns,
-          dontWarnPatterns,
+          dontWarnPatterns.build(),
           rules,
           printSeeds,
           seedFile,
@@ -235,7 +235,7 @@
           DictionaryReader.readAllNames(packageObfuscationDictionary),
           useUniqueClassMemberNames,
           keepParameterNames,
-          adaptClassStrings);
+          adaptClassStrings.build());
     }
   }
 
@@ -257,7 +257,7 @@
   private final boolean verbose;
   private final String renameSourceFileAttribute;
   private final ImmutableList<String> keepAttributesPatterns;
-  private final ProguardClassNameList dontWarnPatterns;
+  private final ProguardClassFilter dontWarnPatterns;
   protected final ImmutableList<ProguardConfigurationRule> rules;
   private final boolean printSeeds;
   private final Path seedFile;
@@ -266,7 +266,7 @@
   private final ImmutableList<String> packageObfuscationDictionary;
   private boolean useUniqueClassMemberNames;
   private boolean keepParameterNames;
-  private final ProguardClassNameList adaptClassStrings;
+  private final ProguardClassFilter adaptClassStrings;
 
   private ProguardConfiguration(
       DexItemFactory factory,
@@ -287,7 +287,7 @@
       boolean verbose,
       String renameSourceFileAttribute,
       List<String> keepAttributesPatterns,
-      ProguardClassNameList dontWarnPatterns,
+      ProguardClassFilter dontWarnPatterns,
       List<ProguardConfigurationRule> rules,
       boolean printSeeds,
       Path seedFile,
@@ -296,7 +296,7 @@
       ImmutableList<String> packageObfuscationDictionary,
       boolean useUniqueClassMemberNames,
       boolean keepParameterNames,
-      ProguardClassNameList adaptClassStrings) {
+      ProguardClassFilter adaptClassStrings) {
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
     this.libraryjars = ImmutableList.copyOf(libraryjars);
@@ -419,7 +419,7 @@
     return keepAttributesPatterns;
   }
 
-  public ProguardClassNameList getDontWarnPatterns() {
+  public ProguardClassFilter getDontWarnPatterns() {
     return dontWarnPatterns;
   }
 
@@ -447,7 +447,7 @@
     return keepParameterNames;
   }
 
-  public ProguardClassNameList getAdaptClassStrings() {
+  public ProguardClassFilter getAdaptClassStrings() {
     return adaptClassStrings;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index ef63e40..40b7598 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -76,6 +76,7 @@
       DexItemFactory dexItemFactory, DiagnosticsHandler diagnosticsHandler) {
     this.dexItemFactory = dexItemFactory;
     configurationBuilder = ProguardConfiguration.builder(dexItemFactory);
+
     this.diagnosticsHandler = diagnosticsHandler;
   }
 
@@ -212,9 +213,9 @@
         configurationBuilder.setIgnoreWarnings(true);
       } else if (acceptString("dontwarn")) {
         if (isOptionalArgumentGiven()) {
-          configurationBuilder.setDontWarnPatterns(parseClassNames());
+          configurationBuilder.addDontWarnPattern(parseClassNames());
         } else {
-          configurationBuilder.setDontWarnPatterns(
+          configurationBuilder.addDontWarnPattern(
               ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
         }
       } else if (acceptString("repackageclasses")) {
@@ -292,9 +293,9 @@
       } else if (acceptString("adaptclassstrings")) {
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
-          configurationBuilder.setAdaptClassStrings(parseClassNames());
+          configurationBuilder.addAdaptClassStringsPattern(parseClassNames());
         } else {
-          configurationBuilder.setAdaptClassStrings(
+          configurationBuilder.addAdaptClassStringsPattern(
               ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
         }
       } else if (acceptString("identifiernamestring")) {
diff --git a/src/test/examples/autovalue/SimpleAutoValue.java b/src/test/examples/autovalue/SimpleAutoValue.java
new file mode 100644
index 0000000..2c0b2c6
--- /dev/null
+++ b/src/test/examples/autovalue/SimpleAutoValue.java
@@ -0,0 +1,54 @@
+// 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 autovalue;
+
+import com.google.auto.value.AutoValue;
+import javax.annotation.Nullable;
+
+public class SimpleAutoValue {
+
+  @AutoValue
+  static abstract class Pair {
+
+    Pair() {
+      // Intentionally left empty.
+    }
+
+    abstract int getOne();
+
+    @Nullable
+    abstract String getOther();
+
+    abstract String getRequiredOther();
+
+    static Builder builder() {
+      return new AutoValue_SimpleAutoValue_Pair.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+
+      abstract Builder setOne(int value);
+
+      abstract Builder setOther(String value);
+
+      abstract Builder setRequiredOther(String value);
+
+      abstract Pair build();
+    }
+  }
+
+  public static void main(String... args) {
+    Pair.Builder builder = Pair.builder();
+    builder.setOne(42);
+    builder.setRequiredOther("123");
+    System.out.println(builder.build());
+    builder = Pair.builder();
+    try {
+      builder.build();
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index a29562a..6a297cd 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -3,9 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -63,19 +68,28 @@
     final String innerClassName = minifiedNames ? "a" : "Minified$Inner";
     final String innerMethodName = minifiedNames ? "a" : "innerTest";
     final String innerSignature = "()I";
-    runDebugTestR8(
-        className,
-        breakpoint(className, methodName, signature),
-        run(),
-        checkMethod(className, methodName, signature),
-        checkLine(SOURCE_FILE, 14),
-        stepOver(),
-        checkMethod(className, methodName, signature),
-        checkLine(SOURCE_FILE, 15),
-        stepInto(),
-        checkMethod(innerClassName, innerMethodName, innerSignature),
-        checkLine(SOURCE_FILE, 8),
-        run());
+    List<Command> commands =
+        new ArrayList<>(
+            Arrays.asList(
+                breakpoint(className, methodName, signature),
+                run(),
+                checkMethod(className, methodName, signature),
+                checkLine(SOURCE_FILE, 14),
+                stepOver(),
+                checkMethod(className, methodName, signature),
+                checkLine(SOURCE_FILE, 15),
+                stepInto()));
+    // Dalvik first enters ClassLoader, step over it.
+    // See also b/67225390.
+    if (ToolHelper.getDexVm() == DexVm.ART_4_4_4_HOST) {
+      commands.add(stepOver());
+    }
+    commands.addAll(
+        Arrays.asList(
+            checkMethod(innerClassName, innerMethodName, innerSignature),
+            checkLine(SOURCE_FILE, 8),
+            run()));
+    runDebugTestR8(className, commands);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 00777fa..7912a9d 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -167,6 +167,25 @@
   }
 
   @Test
+  public void testDontWarnMultiple() throws Exception {
+    DexItemFactory dexItemFactory = new DexItemFactory();
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(dexItemFactory, diagnosticsHandler);
+    List<String> configuration1 = ImmutableList.of("-dontwarn foo.**, bar.**");
+    List<String> configuration2 = ImmutableList.of("-dontwarn foo.**", "-dontwarn bar.**");
+    for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
+      parser.parse(createConfigurationForTesting(configuration));
+      ProguardConfiguration config = parser.getConfig();
+      assertTrue(
+          config.getDontWarnPatterns().matches(dexItemFactory.createType("Lfoo/Bar;")));
+      assertTrue(
+          config.getDontWarnPatterns().matches(dexItemFactory.createType("Lfoo/bar7Bar;")));
+      assertTrue(
+          config.getDontWarnPatterns().matches(dexItemFactory.createType("Lbar/Foo;")));
+    }
+  }
+
+  @Test
   public void testDontWarnAllExplicitly() throws Exception {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
@@ -360,6 +379,26 @@
   }
 
   @Test
+  public void testAdaptClassStringsMultiple() throws Exception {
+    DexItemFactory dexItemFactory = new DexItemFactory();
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(dexItemFactory, diagnosticsHandler);
+    List<String> configuration1 = ImmutableList.of("-adaptclassstrings foo.**, bar.**");
+    List<String> configuration2 =
+        ImmutableList.of("-adaptclassstrings foo.**", "-adaptclassstrings bar.**");
+    for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
+      parser.parse(createConfigurationForTesting(configuration));
+      ProguardConfiguration config = parser.getConfig();
+      assertTrue(
+          config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoo/Bar;")));
+      assertTrue(
+          config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoo/bar7Bar;")));
+      assertTrue(
+          config.getAdaptClassStrings().matches(dexItemFactory.createType("Lbar/Foo;")));
+    }
+  }
+
+  @Test
   public void testAdaptClassStringsAllExplicitly() throws Exception {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
index 2737971..d8c43c9 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
@@ -9,6 +9,9 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
 import org.junit.Test;
 
 public class ProguardNameMatchingTest {
@@ -24,12 +27,20 @@
   }
 
   private static boolean matchClassName(String className, String... patterns) {
-    ProguardClassNameList.Builder builder = ProguardClassNameList.builder();
-    for (String pattern : patterns) {
-      boolean isNegated = pattern.startsWith("!");
-      String actualPattern = isNegated ? pattern.substring(1) : pattern;
-      builder.addClassName(isNegated,
-          ProguardTypeMatcher.create(actualPattern, ClassOrType.CLASS, dexItemFactory));
+    return matchClassName(className, ImmutableList.of(Arrays.asList(patterns)));
+  }
+
+  private static boolean matchClassName(String className, List<List<String>> patternsList) {
+    ProguardClassFilter.Builder builder = ProguardClassFilter.builder();
+    for (List<String> patterns : patternsList) {
+      ProguardClassNameList.Builder listBuilder = ProguardClassNameList.builder();
+      for (String pattern : patterns) {
+        boolean isNegated = pattern.startsWith("!");
+        String actualPattern = isNegated ? pattern.substring(1) : pattern;
+        listBuilder.addClassName(isNegated,
+            ProguardTypeMatcher.create(actualPattern, ClassOrType.CLASS, dexItemFactory));
+      }
+      builder.addPattern(listBuilder.build());
     }
     return builder.build()
         .matches(dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(className)));
@@ -65,6 +76,18 @@
 
     assertTrue(matchClassName("boobar", "!foobar", "*bar"));
     assertFalse(matchClassName("foobar", "!foobar", "*bar"));
+
+    assertFalse(matchClassName("foo", "!boo"));
+    assertFalse(matchClassName("foo", "baz,!boo"));
+
+    assertFalse(matchClassName("boo", "!boo", "**"));
+    assertTrue(matchClassName("boo", "**", "!boo"));
+    assertTrue(matchClassName("boo",
+        ImmutableList.of(ImmutableList.of("!boo"), ImmutableList.of("**"))));
+
+    assertFalse(matchClassName("boofoo", "!boo*,*foo,boofoo"));
+    assertTrue(matchClassName("boofoo",
+        ImmutableList.of(ImmutableList.of("!boo*,*foo"), ImmutableList.of("boofoo"))));
   }
 
   private void assertMatchesBasicTypes(String pattern) {
diff --git a/tools/archive.py b/tools/archive.py
index bb6c984..b88eb8d 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -78,7 +78,7 @@
     version_file = os.path.join(temp, 'r8-version.properties')
     with open(version_file,'w') as version_writer:
       version_writer.write('version.sha=' + GetGitHash() + '\n')
-      version_writer.write('releaser=' + os.environ.get('BUILDBOT_BUILDERNAME') + '\n')
+      version_writer.write('releaser=go/r8bot (' + os.environ.get('BUILDBOT_SLAVENAME') + ')\n')
       version_writer.write('version-file.version.code=1\n')
 
     for jar in [utils.D8_JAR, utils.R8_JAR, utils.COMPATDX_JAR, utils.COMPATPROGUARD_JAR]: