Merge "-printusage part II: print dead code."
diff --git a/src/main/java/com/android/tools/r8/D8Logger.java b/src/main/java/com/android/tools/r8/D8Logger.java
index 25f238b..f0791b0 100644
--- a/src/main/java/com/android/tools/r8/D8Logger.java
+++ b/src/main/java/com/android/tools/r8/D8Logger.java
@@ -10,8 +10,6 @@
 import java.nio.file.Paths;
 import java.util.Arrays;
 
-import static java.util.Arrays.stream;
-
 public final class D8Logger {
 
   private static final int STATUS_ERROR = 1;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 030a478..4867b54 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.AttributeRemovalOptions;
 import com.android.tools.r8.utils.PackageDistribution;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -240,10 +239,6 @@
           appInfo = appInfo.withLiveness().prunedCopyFrom(application);
           new AbstractMethodRemover(appInfo).run();
           new AnnotationRemover(appInfo.withLiveness(), options).run();
-        } else if (!options.skipMinification) {
-          // TODO(38188583): Ensure signatures are removed when minifying.
-          new AnnotationRemover(appInfo.withLiveness(), true,
-              AttributeRemovalOptions.filterOnlySignatures());
         }
       } finally {
         timing.end();
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 9053e9d..4fe3773 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -31,7 +30,6 @@
 public class Bisect {
 
   private final BisectOptions options;
-  private final DexItemFactory factory = new DexItemFactory();
   private final Timing timing = new Timing("bisect");
 
   public interface Command {
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectState.java b/src/main/java/com/android/tools/r8/bisect/BisectState.java
index 148c167..654f1ec 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectState.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -142,7 +142,6 @@
   }
 
   private final String signature;
-  private final DexApplication goodApp;
   private final DexApplication badApp;
   private final List<DexProgramClass> sortedGoodClasses;
   private final Map<DexType, Integer> indexMap;
@@ -154,7 +153,6 @@
   private Range nextRange = null;
 
   public BisectState(DexApplication goodApp, DexApplication badApp, File stateFile) {
-    this.goodApp = goodApp;
     this.badApp = badApp;
     this.stateFile = stateFile;
     signature = makeSignature(goodApp);
diff --git a/src/main/java/com/android/tools/r8/code/Format4rcc.java b/src/main/java/com/android/tools/r8/code/Format4rcc.java
index 7072805..ceac4c8 100644
--- a/src/main/java/com/android/tools/r8/code/Format4rcc.java
+++ b/src/main/java/com/android/tools/r8/code/Format4rcc.java
@@ -56,7 +56,7 @@
       return false;
     }
     Format4rcc o = (Format4rcc) other;
-    return o.AA == AA && o.CCCC == CCCC && o.BBBB.equals(BBBB) & o.HHHH.equals(HHHH);
+    return o.AA == AA && o.CCCC == CCCC && o.BBBB.equals(BBBB) && o.HHHH.equals(HHHH);
   }
 
   public String toString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index c216cec..a5645a1 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -26,7 +26,6 @@
 
   public static final int DEX_MAGIC_SIZE = 8;
 
-  public static final int HEADER_SIZE = 0x70;
   public static final int MAGIC_OFFSET = 0;
   public static final int CHECKSUM_OFFSET = MAGIC_OFFSET + DEX_MAGIC_SIZE;
   public static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + 4;
diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
index 1c37073..768f207 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -771,8 +771,7 @@
           for (int j = 0; j < realHsize; j++) {
             int typeIdx = file.getUleb128();
             int addr = file.getUleb128();
-            pairs[j] = new TypeAddrPair(indexedItems.getType(typeIdx), addr,
-                encodedCatchHandlerOffset);
+            pairs[j] = new TypeAddrPair(indexedItems.getType(typeIdx), addr);
           }
           int catchAllAddr = -1;
           if (hsize <= 0) {
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 b56f712..332f292 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -234,7 +234,7 @@
     layout.setEndOfFile(dest.position());
 
     // Now that we have all mixedSectionOffsets, lets write the indexed items.
-    dest.moveTo(Constants.HEADER_SIZE);
+    dest.moveTo(Constants.TYPE_HEADER_ITEM_SIZE);
     writeFixedSectionItems(mapping.getStrings(), layout.stringIdsOffset, this::writeStringItem);
     writeFixedSectionItems(mapping.getTypes(), layout.typeIdsOffset, this::writeTypeItem);
     writeFixedSectionItems(mapping.getProtos(), layout.protoIdsOffset, this::writeProtoItem);
@@ -790,7 +790,7 @@
     // Leave out checksum and signature for now.
     dest.moveTo(Constants.FILE_SIZE_OFFSET);
     dest.putInt(layout.getEndOfFile());
-    dest.putInt(Constants.HEADER_SIZE);
+    dest.putInt(Constants.TYPE_HEADER_ITEM_SIZE);
     dest.putInt(Constants.ENDIAN_CONSTANT);
     dest.putInt(0);
     dest.putInt(0);
@@ -897,7 +897,7 @@
     static Layout from(ObjectToOffsetMapping mapping) {
       int offset = 0;
       return new Layout(
-          offset = Constants.HEADER_SIZE,
+          offset = Constants.TYPE_HEADER_ITEM_SIZE,
           offset += mapping.getStrings().length * Constants.TYPE_STRING_ID_ITEM_SIZE,
           offset += mapping.getTypes().length * Constants.TYPE_TYPE_ID_ITEM_SIZE,
           offset += mapping.getProtos().length * Constants.TYPE_PROTO_ID_ITEM_SIZE,
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 a029a61..aedb6df 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -11,7 +11,6 @@
 import com.google.common.base.MoreObjects;
 
 import java.util.Arrays;
-import java.util.Comparator;
 import java.util.function.Consumer;
 
 public abstract class DexClass extends DexItem {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 3535bfa..d939868 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -428,12 +428,10 @@
 
       public final DexType type;
       public final /* offset */ int addr;
-      public final /* offset to the start of an encoded_catch_handler. */ int offset;
 
-      public TypeAddrPair(DexType type, int addr, int offset) {
+      public TypeAddrPair(DexType type, int addr) {
         this.type = type;
         this.addr = addr;
-        this.offset = offset;
       }
 
       public void collectIndexedItems(IndexedItemCollection indexedItems) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 4410f2a..39ec7d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -9,6 +9,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Builder to construct a "per position" representation of the debug information.
@@ -160,8 +161,9 @@
 
   private ImmutableMap<Integer, DebugLocalInfo> getLocals() {
     ImmutableMap.Builder<Integer, DebugLocalInfo> builder = ImmutableMap.builder();
-    for (Integer register : locals.keySet()) {
-      LocalEntry entry = locals.get(register);
+    for (Entry<Integer, LocalEntry> mapEntry : locals.entrySet()) {
+      Integer register = mapEntry.getKey();
+      LocalEntry entry = mapEntry.getValue();
       if (entry.current != null) {
         builder.put(register, entry.current);
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c8fbbea..db4a2ab 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -14,10 +14,8 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeDirect;
 import com.android.tools.r8.code.InvokeStatic;
-import com.android.tools.r8.code.InvokeSuper;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.Throw;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.ir.code.IRCode;
@@ -33,7 +31,6 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
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 04e34a5..6500e8f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -228,7 +228,6 @@
     public final DexMethod appendObject;
     public final DexMethod appendString;
     public final DexMethod appendStringBuffer;
-    public final DexMethod toString;
 
     private StringBuildingMethods(DexType receiver) {
       DexType sbufType = createType(createString("Ljava/lang/StringBuffer;"));
@@ -251,7 +250,6 @@
       appendObject = createMethod(receiver, createProto(receiver, objectType), append);
       appendString = createMethod(receiver, createProto(receiver, stringType), append);
       appendStringBuffer = createMethod(receiver, createProto(receiver, sbufType), append);
-      toString = createMethod(receiver, createProto(stringType), toStringMethodName);
     }
 
     public void forEachAppendMethod(Consumer<DexMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 234773f..ac7d9ee 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -364,9 +364,8 @@
     int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
     byte[] content = new byte[newBase.descriptor.content.length + leadingSquareBrackets];
     Arrays.fill(content, 0, leadingSquareBrackets, (byte) '[');
-    for (int i = 0; i < newBase.descriptor.content.length; i++) {
-      content[leadingSquareBrackets + i] = newBase.descriptor.content[i];
-    }
+    System.arraycopy(newBase.descriptor.content, 0, content, leadingSquareBrackets,
+        newBase.descriptor.content.length);
     DexString newDesc = dexItemFactory
         .createString(newBase.descriptor.size + leadingSquareBrackets, content);
     return dexItemFactory.createType(newDesc);
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index a0cfa7c..29f59e5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -593,6 +593,10 @@
       this.values = values;
     }
 
+    public DexValue[] getValues() {
+      return values;
+    }
+
     @Override
     public void collectIndexedItems(IndexedItemCollection indexedItems) {
       collectAll(indexedItems, values);
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index e814dbe..b5a7b75 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -11,7 +11,6 @@
 
 public class DominatorTree {
 
-  IRCode code;
   private BasicBlock[] sorted;
   private BasicBlock[] doms;
 
@@ -21,7 +20,6 @@
 
   // TODO(sgjesse) Get rid of this constructor and blocksToIgnore.
   DominatorTree(IRCode code, List<BasicBlock> blocksToIgnore) {
-    this.code = code;
     this.sorted = code.topologicallySortedBlocks(blocksToIgnore);
     numberBlocks();
     build();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 9e5c972..f38614f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import java.util.Arrays;
 
 public class InstanceGet extends FieldInstruction {
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 8b348a0..4e3f704 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -21,7 +21,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 737c7e5..737dc46 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -588,7 +588,7 @@
           assert i == handlerGroup.getGuards().size() - 1;
           catchAllOffset = targetOffset;
         } else {
-          pairs.add(new TypeAddrPair(type, targetOffset, -1));
+          pairs.add(new TypeAddrPair(type, targetOffset));
         }
       }
       TypeAddrPair[] pairsArray = pairs.toArray(new TypeAddrPair[pairs.size()]);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index a2c1cbd..12d8b6a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -195,7 +195,7 @@
       JarApplicationReader application) {
     DebugLocalInfo info = new DebugLocalInfo(
         application.getString(node.name),
-        application.getType(Type.getType(node.desc)),
+        application.getTypeFromDescriptor(node.desc),
         node.signature == null ? null : application.getString(node.signature));
     DebugLocalInfo canonical = canonicalLocalVariables.putIfAbsent(info, info);
     return canonical != null ? canonical : info;
@@ -892,15 +892,6 @@
     }
   }
 
-  static Type getArrayElementType(Type array) {
-    if (array == JarState.NULL_TYPE) {
-      return null;
-    }
-    String desc = array.getDescriptor();
-    assert desc.charAt(0) == '[';
-    return Type.getType(desc.substring(1));
-  }
-
   private static Type makeArrayType(Type elementType) {
     return Type.getObjectType("[" + elementType.getDescriptor());
   }
@@ -1153,8 +1144,7 @@
       case Opcodes.CALOAD:
       case Opcodes.SALOAD: {
         state.pop();
-        Slot array = state.pop(JarState.ARRAY_TYPE);
-        Type elementType = getArrayElementType(array.type);
+        Type elementType = state.pop(JarState.ARRAY_TYPE).getArrayElementType();
         if (elementType == null) {
           // We propagate the null type, which will then get resolved to an
           // actual type if we have a non-null type on another flow edge.
@@ -1860,7 +1850,7 @@
       case Opcodes.SALOAD: {
         Slot index = state.pop(Type.INT_TYPE);
         Slot array = state.pop(JarState.ARRAY_TYPE);
-        Type elementType = getArrayElementType(array.type);
+        Type elementType = array.getArrayElementType();
         if (elementType == null) {
           elementType = getArrayElementTypeForOpcode(opcode);
         }
@@ -1880,7 +1870,7 @@
         Slot value = state.pop();
         Slot index = state.pop(Type.INT_TYPE);
         Slot array = state.pop(JarState.ARRAY_TYPE);
-        Type elementType = getArrayElementType(array.type);
+        Type elementType = array.getArrayElementType();
         if (elementType == null) {
           elementType = getArrayElementTypeForOpcode(opcode);
         }
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 51a8117..9d11fae 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
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import static com.android.tools.r8.ir.conversion.JarSourceCode.getArrayElementType;
-
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
@@ -18,6 +16,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.LabelNode;
 import org.objectweb.asm.tree.LocalVariableNode;
@@ -37,9 +36,6 @@
   // Type representative for the null value (non-existent but works for tracking the types here).
   public static final Type NULL_TYPE = Type.getObjectType("<null>");
 
-  // Type representative for an address type (used by JSR/RET).
-  public static final Type ADDR_TYPE = Type.getObjectType("<address>");
-
   // Typed mapping from a local slot or stack slot to a virtual register.
   public static class Slot {
     public final int register;
@@ -66,6 +62,14 @@
       return isCategory1(type);
     }
 
+    public Type getArrayElementType() {
+      assert type == NULL_TYPE || type == ARRAY_TYPE || type.getSort() == Type.ARRAY;
+      if (type == JarState.NULL_TYPE) {
+        return null;
+      }
+      return getArrayElementType(type);
+    }
+
     public static boolean isCategory1(Type type) {
       return type != Type.LONG_TYPE && type != Type.DOUBLE_TYPE;
     }
@@ -90,6 +94,12 @@
       return type.equals(other);
     }
 
+    private static Type getArrayElementType(Type type) {
+      String desc = type.getDescriptor();
+      assert desc.charAt(0) == '[';
+      return Type.getType(desc.substring(1));
+    }
+
     private static boolean isIntCompatible(int sort) {
       return Type.BOOLEAN <= sort && sort <= Type.INT;
     }
@@ -357,9 +367,7 @@
     Snapshot snapshot = targetStates.get(offset);
     assert snapshot != null;
     assert locals.length == snapshot.locals.length;
-    for (int i = 0; i < locals.length; i++) {
-      locals[i] = snapshot.locals[i];
-    }
+    System.arraycopy(snapshot.locals, 0, locals, 0, locals.length);
     stack.clear();
     stack.addAll(snapshot.stack);
     topOfStack = startOfStack + 2 * stack.size();
@@ -382,13 +390,14 @@
         }
       }
       // TODO(zerny): Precompute and sort the local ranges.
-      for (LocalVariableNode node : localVariables.keySet()) {
+      for (Entry<LocalVariableNode, DebugLocalInfo> entry : localVariables.entrySet()) {
+        LocalVariableNode node = entry.getKey();
         int startOffset = source.getOffset(node.start);
         int endOffset = source.getOffset(node.end);
         if (startOffset <= target && target < endOffset) {
           int register = getLocalRegister(node.index, Type.getType(node.desc));
           Local local = locals[register];
-          locals[register] = new Local(local.slot, localVariables.get(node));
+          locals[register] = new Local(local.slot, entry.getValue());
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SwitchPayloadResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/SwitchPayloadResolver.java
index 5367a3a..1c20efb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SwitchPayloadResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SwitchPayloadResolver.java
@@ -19,14 +19,12 @@
 
     public final static int NO_SIZE = -1;
     public int userOffset;
-    public int fallthroughOffset;
     public int[] absoluteTargets = null;
     public int[] keys = null;
     public int size = NO_SIZE;
 
-    public PayloadData(int userOffset, int fallthroughOffset) {
+    public PayloadData(int userOffset) {
       this.userOffset = userOffset;
-      this.fallthroughOffset = fallthroughOffset;
     }
   }
 
@@ -36,7 +34,7 @@
   public void addPayloadUser(Instruction dex) {
     int offset = dex.getOffset();
     int payloadOffset = offset + dex.getPayloadOffset();
-    payloadToData.put(payloadOffset, new PayloadData(offset, offset + dex.getSize()));
+    payloadToData.put(payloadOffset, new PayloadData(offset));
     if (unresolvedPayload.containsKey(payloadOffset)) {
       SwitchPayload payload = unresolvedPayload.remove(payloadOffset);
       resolve(payload);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 4a51135..850f37d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -162,7 +162,7 @@
     boolean staticTarget = implHandle.type.isInvokeStatic();
     boolean instanceTarget = implHandle.type.isInvokeInstance();
     boolean initTarget = implHandle.type.isInvokeConstructor();
-    assert instanceTarget || staticTarget | initTarget;
+    assert instanceTarget || staticTarget || initTarget;
 
     if (targetMethod == null) {
       // The target cannot be a private method, since otherwise it
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index fe59b79..0bd7188 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -39,7 +39,6 @@
 import it.unimi.dsi.fastutil.ints.IntSet;
 import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
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 9f26fb5..d8d3b8d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -5,11 +5,17 @@
 
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.naming.signature.GenericSignatureAction;
+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;
@@ -22,8 +28,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
-public class ClassNameMinifier {
+class ClassNameMinifier {
 
   private final AppInfoWithLiveness appInfo;
   private final RootSet rootSet;
@@ -31,11 +38,16 @@
   private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
 
   private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
-  private final Map<String, NamingState> states = new HashMap<>();
+  private final Map<String, ClassNamingState> states = new HashMap<>();
   private final List<String> dictionary;
   private final boolean keepInnerClassStructure;
 
-  public ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
+  private GenericSignatureRewriter genericSignatureRewriter = new GenericSignatureRewriter();
+
+  private GenericSignatureParser<DexType> genericSignatureParser =
+      new GenericSignatureParser<>(genericSignatureRewriter);
+
+  ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
       List<String> dictionary, boolean keepInnerClassStructure) {
     this.appInfo = appInfo;
     this.rootSet = rootSet;
@@ -44,26 +56,61 @@
     this.keepInnerClassStructure = keepInnerClassStructure;
   }
 
-  public Map<DexType, DexString> computeRenaming() {
+  Map<DexType, DexString> computeRenaming() {
     Iterable<DexProgramClass> classes = appInfo.classes();
     // Collect names we have to keep.
-    for (DexClass clazz : appInfo.classes()) {
+    for (DexClass clazz : classes) {
       if (rootSet.noObfuscation.contains(clazz)) {
         assert !renaming.containsKey(clazz.type);
         registerClassAsUsed(clazz.type);
       }
     }
-    for (DexClass clazz : appInfo.classes()) {
+    for (DexClass clazz : classes) {
       if (!renaming.containsKey(clazz.type)) {
         DexString renamed = computeName(clazz);
         renaming.put(clazz.type, renamed);
       }
     }
+
+    renameTypesInGenericSignatures();
+
     appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
 
     return Collections.unmodifiableMap(renaming);
   }
 
+  private void renameTypesInGenericSignatures() {
+    for (DexClass clazz : appInfo.classes()) {
+      rewriteGenericSignatures(clazz.annotations.annotations,
+          genericSignatureParser::parseClassSignature);
+      clazz.forEachField(field -> rewriteGenericSignatures(
+          field.annotations.annotations, genericSignatureParser::parseFieldSignature));
+      clazz.forEachMethod(method -> rewriteGenericSignatures(
+          method.annotations.annotations, genericSignatureParser::parseMethodSignature));
+    }
+  }
+
+  private void rewriteGenericSignatures(DexAnnotation[] annotations, Consumer<String> parser) {
+    for (int i = 0; i < annotations.length; i++) {
+      DexAnnotation annotation = annotations[i];
+      if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
+        parser.accept(getSignatureFromAnnotation(annotation));
+        annotations[i] = DexAnnotation.createSignatureAnnotation(
+            genericSignatureRewriter.getRenamedSignature(),
+            appInfo.dexItemFactory);
+      }
+    }
+  }
+
+  private static String getSignatureFromAnnotation(DexAnnotation signatureAnnotation) {
+    DexValueArray elements = (DexValueArray) signatureAnnotation.annotation.elements[0].value;
+    StringBuilder signature = new StringBuilder();
+    for (DexValue element : elements.getValues()) {
+      signature.append(((DexValueString) element).value.toString());
+    }
+    return signature.toString();
+  }
+
   /**
    * Registers the given type as used.
    * <p>
@@ -105,7 +152,7 @@
   }
 
   private DexString computeName(DexClass clazz) {
-    NamingState state = null;
+    ClassNamingState 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.
@@ -129,11 +176,11 @@
     }
   }
 
-  private NamingState getStateFor(String packageName) {
-    return states.computeIfAbsent(packageName, NamingState::new);
+  private ClassNamingState getStateFor(String packageName) {
+    return states.computeIfAbsent(packageName, ClassNamingState::new);
   }
 
-  private NamingState getStateForOuterClass(DexType outer) {
+  private ClassNamingState getStateForOuterClass(DexType outer) {
     String prefix = DescriptorUtils
         .getClassBinaryNameFromDescriptor(outer.toDescriptorString());
     return states.computeIfAbsent(prefix, k -> {
@@ -150,7 +197,7 @@
         }
       }
       String binaryName = DescriptorUtils.getClassBinaryNameFromDescriptor(renamed.toString());
-      return new NamingState(binaryName, "$");
+      return new ClassNamingState(binaryName, "$");
     });
   }
 
@@ -171,21 +218,19 @@
     }
   }
 
-  private class NamingState {
+  private class ClassNamingState {
 
     private final char[] packagePrefix;
-    private final String separator;
     private int typeCounter = 1;
     private Iterator<String> dictionaryIterator;
 
-    NamingState(String packageName) {
+    ClassNamingState(String packageName) {
       this(packageName, "/");
     }
 
-    NamingState(String packageName, String separator) {
+    ClassNamingState(String packageName, String separator) {
       this.packagePrefix = ("L" + packageName + (packageName.isEmpty() ? "" : separator))
           .toCharArray();
-      this.separator = separator;
       this.dictionaryIterator = dictionary.iterator();
     }
 
@@ -211,4 +256,63 @@
       return candidate;
     }
   }
+
+  private class GenericSignatureRewriter implements GenericSignatureAction<DexType> {
+
+    private StringBuilder renamedSignature;
+
+    public String getRenamedSignature() {
+      return renamedSignature.toString();
+    }
+
+    @Override
+    public void parsedSymbol(char symbol) {
+      renamedSignature.append(symbol);
+    }
+
+    @Override
+    public void parsedIdentifier(String identifier) {
+      renamedSignature.append(identifier);
+    }
+
+    @Override
+    public DexType parsedTypeName(String name) {
+      DexType type = appInfo.dexItemFactory.createType(
+          DescriptorUtils.getDescriptorFromClassBinaryName(name));
+      DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
+      renamedSignature.append(DescriptorUtils.getClassBinaryNameFromDescriptor(
+          renamedDescriptor.toString()));
+      return type;
+    }
+
+    @Override
+    public DexType parsedInnerTypeName(DexType enclosingType, String name) {
+      assert enclosingType.isClassType();
+      String enclosingDescriptor = enclosingType.toDescriptorString();
+      DexType type =
+          appInfo.dexItemFactory.createType(
+              DescriptorUtils.getDescriptorFromClassBinaryName(
+                  DescriptorUtils.getClassBinaryNameFromDescriptor(enclosingDescriptor)
+                  + '$' + name));
+      String enclosingRenamedBinaryName =
+          DescriptorUtils.getClassBinaryNameFromDescriptor(renaming.getOrDefault(enclosingType,
+              enclosingType.descriptor).toString());
+      String renamed = DescriptorUtils.getClassBinaryNameFromDescriptor(
+          renaming.getOrDefault(type, type.descriptor).toString());
+      assert renamed.startsWith(enclosingRenamedBinaryName + '$');
+      String outName = renamed.substring(enclosingRenamedBinaryName.length() + 1);
+      renamedSignature.append(outName);
+      return type;
+    }
+
+    @Override
+    public void start() {
+      renamedSignature = new StringBuilder();
+    }
+
+    @Override
+    public void stop() {
+      // nothing to do
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 3a72512..8177e89 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -14,7 +14,7 @@
 import java.util.List;
 import java.util.Map;
 
-public class FieldNameMinifier {
+class FieldNameMinifier {
 
   private final AppInfoWithSubtyping appInfo;
   private final RootSet rootSet;
@@ -22,7 +22,7 @@
   private final List<String> dictionary;
   private final Map<DexType, NamingState<DexType>> states = new IdentityHashMap<>();
 
-  public FieldNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, List<String> dictionary) {
+  FieldNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, List<String> dictionary) {
     this.appInfo = appInfo;
     this.rootSet = rootSet;
     this.dictionary = dictionary;
@@ -50,18 +50,17 @@
       return;
     }
     NamingState<DexType> newState = states.computeIfAbsent(type, t -> state.createChild());
-    reserveFieldNames(newState, holder.instanceFields(), holder.isLibraryClass());
-    reserveFieldNames(newState, holder.staticFields(), holder.isLibraryClass());
+    holder.forEachField(field -> reserveFieldName(field, newState, holder.isLibraryClass()));
     type.forAllExtendsSubtypes(subtype -> reserveNamesInSubtypes(subtype, newState));
   }
 
-  private void reserveFieldNames(NamingState<DexType> state, DexEncodedField[] fields,
+  private void reserveFieldName(
+      DexEncodedField encodedField,
+      NamingState<DexType> state,
       boolean isLibrary) {
-    for (DexEncodedField encodedField : fields) {
-      if (isLibrary || rootSet.noObfuscation.contains(encodedField)) {
-        DexField field = encodedField.field;
-        state.reserveName(field.name, field.type);
-      }
+    if (isLibrary || rootSet.noObfuscation.contains(encodedField)) {
+      DexField field = encodedField.field;
+      state.reserveName(field.name, field.type);
     }
   }
 
@@ -72,17 +71,14 @@
     }
     NamingState<DexType> state = states.get(clazz.type);
     assert state != null;
-    renameFields(clazz.instanceFields(), state);
-    renameFields(clazz.staticFields(), state);
+    clazz.forEachField(field -> renameField(field, state));
     type.forAllExtendsSubtypes(this::renameFieldsInSubtypes);
   }
 
-  private void renameFields(DexEncodedField[] fields, NamingState<DexType> state) {
-    for (DexEncodedField encodedField : fields) {
-      DexField field = encodedField.field;
-      if (!state.isReserved(field.name, field.type)) {
-        renaming.put(field, state.assignNewNameFor(field.name, field.type, false));
-      }
+  private void renameField(DexEncodedField encodedField, NamingState<DexType> state) {
+    DexField field = encodedField.field;
+    if (!state.isReserved(field.name, field.type)) {
+      renaming.put(field, state.assignNewNameFor(field.name, field.type, false));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index ec7fa46..4aca460 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -84,7 +84,7 @@
  * TODO(herhut): Currently, we do not minify members of annotation interfaces, as this would require
  * parsing and minification of the string arguments to annotations.
  */
-public class MethodNameMinifier {
+class MethodNameMinifier {
 
   private final AppInfoWithSubtyping appInfo;
   private final RootSet rootSet;
@@ -93,7 +93,7 @@
   private MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
   private final List<String> dictionary;
 
-  public MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet,
+  MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet,
       List<String> dictionary) {
     this.appInfo = appInfo;
     this.rootSet = rootSet;
@@ -101,7 +101,7 @@
     this.globalState = NamingState.createRoot(appInfo.dexItemFactory, dictionary);
   }
 
-  public Map<DexMethod, DexString> computeRenaming(Timing timing) {
+  Map<DexMethod, DexString> computeRenaming(Timing timing) {
     // Phase 1: Reserve all the names that need to be kept and allocate linked state in the
     //          library part.
     timing.begin("Phase 1");
@@ -198,10 +198,8 @@
       DexClass clazz = appInfo.definitionFor(iface);
       if (clazz != null) {
         Set<NamingState<DexProto>> collectedStates = getReachableStates(iface, frontierMap);
-        addStatesToGlobalMapForMethods(clazz.directMethods(), collectedStates, globalStateMap,
-            sourceMethodsMap, originStates, iface);
-        addStatesToGlobalMapForMethods(clazz.virtualMethods(), collectedStates, globalStateMap,
-            sourceMethodsMap, originStates, iface);
+        clazz.forEachMethod(method -> addStatesToGlobalMapForMethod(
+            method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface));
       }
     });
     timing.end();
@@ -237,7 +235,6 @@
 
 
   private void collectSubInterfaces(DexType iface, Set<DexType> interfaces) {
-    DexClass clazz = appInfo.definitionFor(iface);
     iface.forAllExtendsSubtypes(subtype -> {
       assert subtype.isInterface();
       if (interfaces.add(subtype)) {
@@ -246,19 +243,17 @@
     });
   }
 
-  private void addStatesToGlobalMapForMethods(
-      DexEncodedMethod[] methods, Set<NamingState<DexProto>> collectedStates,
+  private void addStatesToGlobalMapForMethod(
+      DexEncodedMethod method, Set<NamingState<DexProto>> collectedStates,
       Map<Wrapper<DexMethod>, Set<NamingState<DexProto>>> globalStateMap,
       Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap,
       Map<Wrapper<DexMethod>, NamingState<DexProto>> originStates, DexType originInterface) {
-    for (DexEncodedMethod method : methods) {
-      Wrapper<DexMethod> key = equivalence.wrap(method.method);
-      Set<NamingState<DexProto>> stateSet = globalStateMap
-          .computeIfAbsent(key, k -> new HashSet<>());
-      stateSet.addAll(collectedStates);
-      sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method);
-      originStates.putIfAbsent(key, states.get(originInterface));
-    }
+    Wrapper<DexMethod> key = equivalence.wrap(method.method);
+    Set<NamingState<DexProto>> stateSet =
+        globalStateMap.computeIfAbsent(key, k -> new HashSet<>());
+    stateSet.addAll(collectedStates);
+    sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method);
+    originStates.putIfAbsent(key, states.get(originInterface));
   }
 
   private void assignNameForInterfaceMethodInAllStates(DexMethod method,
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
new file mode 100644
index 0000000..6753e06
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
@@ -0,0 +1,23 @@
+// 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.naming.signature;
+
+/**
+ * Actions triggered by the generic signature parser.
+ */
+public interface GenericSignatureAction<T> {
+
+  public void parsedSymbol(char symbol);
+
+  public void parsedIdentifier(String identifier);
+
+  public T parsedTypeName(String name);
+
+  public T parsedInnerTypeName(T enclosingType, String name);
+
+  public void start();
+
+  public void stop();
+}
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
new file mode 100644
index 0000000..c5d78f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -0,0 +1,403 @@
+// Copyright (c) 2016, 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.naming.signature;
+
+import java.lang.reflect.GenericSignatureFormatError;
+
+/**
+ * Implements a parser for the generics signature attribute as defined by JVMS 7 $ 4.3.4.
+ * Uses a top-down, recursive descent parsing approach for the following grammar:
+ * <pre>
+ * ClassSignature ::=
+ *     OptFormalTypeParams SuperclassSignature {SuperinterfaceSignature}.
+ * SuperclassSignature ::= ClassTypeSignature.
+ * SuperinterfaceSignature ::= ClassTypeSignature.
+ *
+ * OptFormalTypeParams ::=
+ *     ["<" FormalTypeParameter {FormalTypeParameter} ">"].
+ *
+ * FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
+ * ClassBound ::= ":" [FieldTypeSignature].
+ * InterfaceBound ::= ":" FieldTypeSignature.
+ *
+ * FieldTypeSignature ::=
+ *     ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
+ * ArrayTypeSignature ::= "[" TypSignature.
+ *
+ * ClassTypeSignature ::=
+ *     "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments} ";".
+ *
+ * OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">".
+ *
+ * TypeArgument ::= ([WildcardIndicator] FieldTypeSignature) | "*".
+ * WildcardIndicator ::= "+" | "-".
+ *
+ * TypeVariableSignature ::= "T" Ident ";".
+ *
+ * TypSignature ::= FieldTypeSignature | BaseType.
+ * BaseType ::= "B" | "C" | "D" | "F" | "I" | "J" | "S" | "Z".
+ *
+ * MethodTypeSignature ::=
+ *     OptFormalTypeParams "(" {TypeSignature} ")" ReturnType {ThrowsSignature}.
+ * ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature).
+ *
+ * ReturnType ::= TypSignature | VoidDescriptor.
+ * VoidDescriptor ::= "V".
+ * </pre>
+ */
+public class GenericSignatureParser<T> {
+
+  private final GenericSignatureAction<T> actions;
+
+  /*
+   * Parser:
+   */
+  private char symbol; // 0: eof; else valid term symbol or first char of identifier.
+
+  private String identifier;
+
+  /*
+   * Scanner:
+   * eof is private to the scan methods
+   * and it's set only when a scan is issued at the end of the buffer.
+   */
+  private boolean eof;
+
+  private char[] buffer;
+
+  private int pos;
+
+  public GenericSignatureParser(GenericSignatureAction<T> actions) {
+    this.actions = actions;
+  }
+
+  public void parseClassSignature(String signature) {
+    actions.start();
+    setInput(signature);
+    parseClassSignature();
+    actions.stop();
+  }
+
+  public void parseMethodSignature(String signature) {
+    actions.start();
+    setInput(signature);
+    parseMethodTypeSignature();
+    actions.stop();
+  }
+
+  public void parseFieldSignature(String signature) {
+    actions.start();
+    setInput(signature);
+    parseFieldTypeSignature();
+    actions.stop();
+  }
+
+  private void setInput(String input) {
+    this.buffer = input.toCharArray();
+    this.eof = false;
+    pos = 0;
+    symbol = 0;
+    identifier = null;
+    scanSymbol();
+  }
+
+  //
+  // Parser:
+  //
+
+  void parseClassSignature() {
+    // ClassSignature ::= OptFormalTypeParameters SuperclassSignature {SuperinterfaceSignature}.
+
+    parseOptFormalTypeParameters();
+
+    // SuperclassSignature ::= ClassTypeSignature.
+    parseClassTypeSignature();
+
+    while (symbol > 0) {
+      // SuperinterfaceSignature ::= ClassTypeSignature.
+      parseClassTypeSignature();
+    }
+  }
+
+  void parseOptFormalTypeParameters() {
+    // OptFormalTypeParameters ::= ["<" FormalTypeParameter {FormalTypeParameter} ">"].
+
+    if (symbol == '<') {
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+
+      updateFormalTypeParameter();
+
+      while ((symbol != '>') && (symbol > 0)) {
+        updateFormalTypeParameter();
+      }
+
+      actions.parsedSymbol(symbol);
+      expect('>');
+    }
+  }
+
+  void updateFormalTypeParameter() {
+    // FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
+    scanIdentifier();
+    assert identifier != null;
+    actions.parsedIdentifier(identifier);
+
+    // ClassBound ::= ":" [FieldTypeSignature].
+    actions.parsedSymbol(symbol);
+    expect(':');
+
+    if (symbol == 'L' || symbol == '[' || symbol == 'T') {
+      parseFieldTypeSignature();
+    }
+
+    while (symbol == ':') {
+      // InterfaceBound ::= ":" FieldTypeSignature.
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+      parseFieldTypeSignature();
+    }
+  }
+
+  private void parseFieldTypeSignature() {
+    // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
+    switch (symbol) {
+      case 'L':
+        parseClassTypeSignature();
+        break;
+      case '[':
+        // ArrayTypeSignature ::= "[" TypSignature.
+        actions.parsedSymbol(symbol);
+        scanSymbol();
+        updateTypeSignature();
+        break;
+      case 'T':
+        updateTypeVariableSignature();
+        break;
+      default:
+        throw new GenericSignatureFormatError();
+    }
+  }
+
+  private void parseClassTypeSignature() {
+    // ClassTypeSignature ::= "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments}
+    //  ";".
+    actions.parsedSymbol(symbol);
+    expect('L');
+
+    StringBuilder qualIdent = new StringBuilder();
+    scanIdentifier();
+    assert identifier != null;
+    while (symbol == '/') {
+      qualIdent.append(identifier).append(symbol);
+      scanSymbol();
+      scanIdentifier();
+      assert identifier != null;
+    }
+
+    qualIdent.append(this.identifier);
+    T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString());
+
+    updateOptTypeArguments();
+
+    while (symbol == '.') {
+      // Deal with Member Classes:
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+      scanIdentifier();
+      assert identifier != null;
+      parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
+      updateOptTypeArguments();
+    }
+
+    actions.parsedSymbol(symbol);
+    expect(';');
+  }
+
+  private void updateOptTypeArguments() {
+    // OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">".
+    if (symbol == '<') {
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+
+      updateTypeArgument();
+      while ((symbol != '>') && (symbol > 0)) {
+        updateTypeArgument();
+      }
+
+      actions.parsedSymbol(symbol);
+      expect('>');
+    }
+  }
+
+  private void updateTypeArgument() {
+    // TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*".
+    if (symbol == '*') {
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+    } else if (symbol == '+') {
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+      parseFieldTypeSignature();
+    } else if (symbol == '-') {
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+      parseFieldTypeSignature();
+    } else {
+      parseFieldTypeSignature();
+    }
+  }
+
+ private  void updateTypeVariableSignature() {
+    // TypeVariableSignature ::= "T" Ident ";".
+    actions.parsedSymbol(symbol);
+    expect('T');
+
+    scanIdentifier();
+    assert identifier != null;
+    actions.parsedIdentifier(identifier);
+
+    actions.parsedSymbol(symbol);
+    expect(';');
+  }
+
+  private void updateTypeSignature() {
+    switch (symbol) {
+      case 'B':
+      case 'C':
+      case 'D':
+      case 'F':
+      case 'I':
+      case 'J':
+      case 'S':
+      case 'Z':
+        actions.parsedSymbol(symbol);
+        scanSymbol();
+        break;
+      default:
+        // Not an elementary type, but a FieldTypeSignature.
+        parseFieldTypeSignature();
+    }
+  }
+
+  private void parseMethodTypeSignature() {
+    // MethodTypeSignature ::= [FormalTypeParameters] "(" {TypeSignature} ")" ReturnType
+    //  {ThrowsSignature}.
+    parseOptFormalTypeParameters();
+
+    actions.parsedSymbol(symbol);
+    expect('(');
+
+    while (symbol != ')' && (symbol > 0)) {
+      updateTypeSignature();
+    }
+
+    actions.parsedSymbol(symbol);
+    expect(')');
+
+    updateReturnType();
+
+    if (symbol == '^') {
+      do {
+        actions.parsedSymbol(symbol);
+        scanSymbol();
+
+        // ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature).
+        if (symbol == 'T') {
+          updateTypeVariableSignature();
+        } else {
+          parseClassTypeSignature();
+        }
+      } while (symbol == '^');
+    }
+  }
+
+  private void updateReturnType() {
+    // ReturnType ::= TypeSignature | "V".
+    if (symbol != 'V') {
+      updateTypeSignature();
+    } else {
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+    }
+  }
+
+
+  //
+  // Scanner:
+  //
+
+  private void scanSymbol() {
+    if (!eof) {
+      assert buffer != null;
+      if (pos < buffer.length) {
+        symbol = buffer[pos];
+        pos++;
+      } else {
+        symbol = 0;
+        eof = true;
+      }
+    } else {
+      throw new GenericSignatureFormatError();
+    }
+  }
+
+  private void expect(char c) {
+    if (symbol == c) {
+      scanSymbol();
+    } else {
+      throw new GenericSignatureFormatError();
+    }
+  }
+
+ private  boolean isStopSymbol(char ch) {
+    switch (ch) {
+      case ':':
+      case '/':
+      case ';':
+      case '<':
+      case '.':
+        return true;
+    }
+    return false;
+  }
+
+  // PRE: symbol is the first char of the identifier.
+  // POST: symbol = the next symbol AFTER the identifier.
+  private void scanIdentifier() {
+    if (!eof) {
+      StringBuilder identBuf = new StringBuilder(32);
+      if (!isStopSymbol(symbol)) {
+        identBuf.append(symbol);
+
+        // FINDBUGS
+        char[] bufferLocal = buffer;
+        assert bufferLocal != null;
+        do {
+          char ch = bufferLocal[pos];
+          if ((ch >= 'a') && (ch <= 'z') || (ch >= 'A') && (ch <= 'Z')
+              || !isStopSymbol(ch)) {
+            identBuf.append(bufferLocal[pos]);
+            pos++;
+          } else {
+            identifier = identBuf.toString();
+            scanSymbol();
+            return;
+          }
+        } while (pos != bufferLocal.length);
+        identifier = identBuf.toString();
+        symbol = 0;
+        eof = true;
+      } else {
+        // Ident starts with incorrect char.
+        symbol = 0;
+        eof = true;
+        throw new GenericSignatureFormatError();
+      }
+    } else {
+      throw new GenericSignatureFormatError();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 4cd44ef..3106c0c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -21,17 +21,15 @@
 public class AnnotationRemover {
 
   private final AppInfoWithLiveness appInfo;
-  private final boolean minificationEnabled;
   private final AttributeRemovalOptions keep;
 
   public AnnotationRemover(AppInfoWithLiveness appInfo, InternalOptions options) {
-    this(appInfo, !options.skipMinification, options.attributeRemoval);
+    this(appInfo, options.attributeRemoval);
   }
 
-  public AnnotationRemover(AppInfoWithLiveness appInfo, boolean minificationEnabled,
+  public AnnotationRemover(AppInfoWithLiveness appInfo,
       AttributeRemovalOptions keep) {
     this.appInfo = appInfo;
-    this.minificationEnabled = minificationEnabled;
     this.keep = keep;
   }
 
@@ -104,7 +102,7 @@
   }
 
   public void run() {
-    keep.ensureValid(minificationEnabled);
+    keep.ensureValid();
     for (DexProgramClass clazz : appInfo.classes()) {
       clazz.annotations = stripAnnotations(clazz.annotations, this::filterAnnotations);
       clazz.forEachMethod(this::processMethod);
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index a7a27fe..e675525 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import java.util.Arrays;
 import java.util.Set;
 
 public class DiscardedChecker {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index f6ac140..b589535 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -11,10 +11,8 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index eae33d1..277ee0f 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -148,6 +148,18 @@
     return classDescriptor.substring(1, classDescriptor.length() - 1);
   }
 
+
+  /**
+   * Convert a class binary name to a descriptor.
+   *
+   * @param typeBinaryName class binary name i.e. "java/lang/Object"
+   * @return a class descriptor i.e. "Ljava/lang/Object;"
+   */
+  public static String getDescriptorFromClassBinaryName(String typeBinaryName) {
+    return ('L' + typeBinaryName + ';');
+  }
+
+
   /**
    * Get class name from its binary name.
    *
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 7155a13..d1f8290 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -7,14 +7,12 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.conversion.CallGraph;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardTypeMatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
 import java.util.List;
-import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public class InternalOptions {
@@ -249,7 +247,7 @@
       annotationDefault = update(annotationDefault, ANNOTATION_DEFAULT, patterns);
     }
 
-    public void ensureValid(boolean isMinifying) {
+    public void ensureValid() {
       if (innerClasses && !enclosingMethod) {
         throw new CompilationError("Attribute InnerClasses requires EnclosingMethod attribute. "
             + "Check -keepattributes directive.");
@@ -259,10 +257,6 @@
       } else if (signature && !innerClasses) {
         throw new CompilationError("Attribute Signature requires InnerClasses attribute. Check "
             + "-keepattributes directive.");
-      } else if (signature && isMinifying) {
-        // TODO(38188583): Allow this once we can minify signatures.
-        throw new CompilationError("Attribute Signature cannot be kept when minifying. "
-            + "Check -keepattributes directive.");
       }
     }
   }
diff --git a/src/test/examples/minifygeneric/AA.java b/src/test/examples/minifygeneric/AA.java
new file mode 100644
index 0000000..66b7aaf8
--- /dev/null
+++ b/src/test/examples/minifygeneric/AA.java
@@ -0,0 +1,7 @@
+// 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 minifygeneric;
+
+public class AA {
+}
diff --git a/src/test/examples/minifygeneric/BB.java b/src/test/examples/minifygeneric/BB.java
new file mode 100644
index 0000000..c29ca97
--- /dev/null
+++ b/src/test/examples/minifygeneric/BB.java
@@ -0,0 +1,7 @@
+// 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 minifygeneric;
+
+public class BB {
+}
diff --git a/src/test/examples/minifygeneric/Generic.java b/src/test/examples/minifygeneric/Generic.java
new file mode 100644
index 0000000..53f9e3d
--- /dev/null
+++ b/src/test/examples/minifygeneric/Generic.java
@@ -0,0 +1,32 @@
+// 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 minifygeneric;
+
+import java.util.List;
+import java.util.Map;
+
+public class Generic<T extends AA> {
+
+  public <U extends List> T m (Object o, T[] t, Map<T,U> m) {
+    return null;
+  }
+
+  public <U extends Map> T m2 (Object o, T[] t, Map<T,U> m) {
+    return null;
+  }
+
+  public <U extends List> T m3 (Object o, T t, Map<T,U> m) {
+    return null;
+  }
+
+  public <U extends List> T m4 (Object o, T[] t, List<U> m) {
+    return null;
+  }
+
+  public Generic<T> f;
+  public Generic<T> get() {
+    return this;
+  }
+
+}
diff --git a/src/test/examples/minifygeneric/Minifygeneric.java b/src/test/examples/minifygeneric/Minifygeneric.java
new file mode 100644
index 0000000..36406bb
--- /dev/null
+++ b/src/test/examples/minifygeneric/Minifygeneric.java
@@ -0,0 +1,42 @@
+// 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 minifygeneric;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class Minifygeneric {
+
+  public static void main(String[] args) throws NoSuchMethodException, SecurityException, NoSuchFieldException {
+    for (TypeVariable<Class<Generic>> var : Generic.class.getTypeParameters()) {
+      System.out.println(var.getName());
+      Type bound = var.getBounds()[0];
+      System.out.println(((Class<?>) bound).getName().equals(AA.class.getName()));
+    }
+
+    Field f = Generic.class.getField("f");
+    ParameterizedType fieldType = (java.lang.reflect.ParameterizedType)f.getGenericType();
+    checkOneParameterType(fieldType, Generic.class, AA.class);
+
+    ParameterizedType methodReturnType =
+        (ParameterizedType) Generic.class.getMethod("get").getGenericReturnType();
+    checkOneParameterType(methodReturnType, Generic.class, AA.class);
+  }
+
+  private static void checkOneParameterType(ParameterizedType toCheck, Class<?> rawType,
+      Class<?>... bounds) {
+    System.out.println(((Class<?>) toCheck.getRawType()).getName()
+        .equals(rawType.getName()));
+    Type[] parameters = toCheck.getActualTypeArguments();
+    System.out.println(parameters.length);
+    TypeVariable<?> parameter = (TypeVariable<?>) parameters[0];
+    System.out.println(parameter.getName());
+    Type[] actualBounds = parameter.getBounds();
+    for (int i = 0; i < bounds.length; i++) {
+      System.out.println(((Class<?>) actualBounds[i]).getName().equals(bounds[i].getName()));
+    }
+  }
+}
diff --git a/src/test/examples/minifygeneric/keep-rules.txt b/src/test/examples/minifygeneric/keep-rules.txt
new file mode 100644
index 0000000..741d5c3
--- /dev/null
+++ b/src/test/examples/minifygeneric/keep-rules.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.
+
+-keep,allowobfuscation class ** {
+*;
+}
+-keep class *.Minifygeneric {
+*;
+}
+-keepclassmembernames class *.Generic {
+*;
+}
+-keepattributes *
+-allowaccessmodification
diff --git a/src/test/examples/minifygenericwithinner/AA.java b/src/test/examples/minifygenericwithinner/AA.java
new file mode 100644
index 0000000..ff283da
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/AA.java
@@ -0,0 +1,7 @@
+// 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 minifygenericwithinner;
+
+public class AA {
+}
diff --git a/src/test/examples/minifygenericwithinner/BB.java b/src/test/examples/minifygenericwithinner/BB.java
new file mode 100644
index 0000000..31ceb33
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/BB.java
@@ -0,0 +1,7 @@
+// 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 minifygenericwithinner;
+
+public class BB {
+}
diff --git a/src/test/examples/minifygenericwithinner/Generic.java b/src/test/examples/minifygenericwithinner/Generic.java
new file mode 100644
index 0000000..203561e
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/Generic.java
@@ -0,0 +1,42 @@
+// 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 minifygenericwithinner;
+
+import java.util.List;
+import java.util.Map;
+
+public class Generic<T extends AA> {
+
+  public <U extends List> T m (Object o, T[] t, Map<T,U> m) {
+    return null;
+  }
+
+  public <U extends Map> T m2 (Object o, T[] t, Map<T,U> m) {
+    return null;
+  }
+
+  public <U extends List> T m3 (Object o, T t, Map<T,U> m) {
+    return null;
+  }
+
+  public <U extends List> T m4 (Object o, T[] t, List<U> m) {
+    return null;
+  }
+
+  public <V extends BB> Inner<V> getInner(V obj) {
+    return new Inner<>();
+  }
+
+  public class Inner<V extends BB> {
+
+    public Generic<T>.Inner<V> f;
+    public <U extends List> T m5 (V o, T[] t, Map<T,U> m) {
+      return m(o, t, m);
+    }
+
+   public Generic<T>.Inner<V> get() {
+      return this;
+    }
+  }
+}
diff --git a/src/test/examples/minifygenericwithinner/Minifygenericwithinner.java b/src/test/examples/minifygenericwithinner/Minifygenericwithinner.java
new file mode 100644
index 0000000..882fa83
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/Minifygenericwithinner.java
@@ -0,0 +1,52 @@
+// 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 minifygenericwithinner;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class Minifygenericwithinner {
+
+  public static void main(String[] args)
+      throws NoSuchMethodException, SecurityException, NoSuchFieldException {
+    for (TypeVariable<Class<Generic>> var : Generic.class.getTypeParameters()) {
+      System.out.println(var.getName());
+      Type bound = var.getBounds()[0];
+      System.out.println(((Class<?>) bound).getName().equals(AA.class.getName()));
+    }
+    for (TypeVariable<Class<Generic.Inner>> var : Generic.Inner.class.getTypeParameters()) {
+      System.out.println(var.getName());
+      Type bound = var.getBounds()[0];
+      System.out.println(((Class<?>) bound).getName().equals(BB.class.getName()));
+    }
+
+    Field f = Generic.Inner.class.getField("f");
+    ParameterizedType fieldType = (java.lang.reflect.ParameterizedType)f.getGenericType();
+    checkOneParameterType(fieldType, Generic.Inner.class, BB.class);
+    ParameterizedType ownerType = (ParameterizedType) fieldType.getOwnerType();
+    checkOneParameterType(ownerType, Generic.class, AA.class);
+
+    ParameterizedType methodReturnType =
+        (ParameterizedType) Generic.Inner.class.getMethod("get").getGenericReturnType();
+    checkOneParameterType(methodReturnType, Generic.Inner.class, BB.class);
+    ownerType = (ParameterizedType) methodReturnType.getOwnerType();
+    checkOneParameterType(ownerType, Generic.class, AA.class);
+  }
+
+  private static void checkOneParameterType(ParameterizedType toCheck, Class<?> rawType,
+      Class<?>... bounds) {
+    System.out.println(((Class<?>) toCheck.getRawType()).getName()
+        .equals(rawType.getName()));
+    Type[] parameters = toCheck.getActualTypeArguments();
+    System.out.println(parameters.length);
+    TypeVariable<?> parameter = (TypeVariable<?>) parameters[0];
+    System.out.println(parameter.getName());
+    Type[] actualBounds = parameter.getBounds();
+    for (int i = 0; i < bounds.length; i++) {
+      System.out.println(((Class<?>) actualBounds[i]).getName().equals(bounds[i].getName()));
+    }
+  }
+}
diff --git a/src/test/examples/minifygenericwithinner/keep-rules.txt b/src/test/examples/minifygenericwithinner/keep-rules.txt
new file mode 100644
index 0000000..357b321
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/keep-rules.txt
@@ -0,0 +1,16 @@
+# 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.
+
+-keep,allowobfuscation class ** {
+*;
+}
+-keep class *.Minifygenericwithinner {
+*;
+}
+-keepclassmembernames class *.Generic$* {
+*;
+}
+
+-keepattributes *
+-allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 283d424..5b94e6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -69,7 +69,12 @@
       "shaking16:keep-rules-2.txt:DEX:false",
       "shaking16:keep-rules-2.txt:JAR:false",
       "shaking15:keep-rules.txt:DEX:false",
-      "shaking15:keep-rules.txt:JAR:false"
+      "shaking15:keep-rules.txt:JAR:false",
+      "minifygeneric:keep-rules.txt:DEX:false",
+      "minifygeneric:keep-rules.txt:JAR:false",
+      "minifygenericwithinner:keep-rules.txt:DEX:false",
+      "minifygenericwithinner:keep-rules.txt:JAR:false"
+
   );
   private final boolean minify;
 
@@ -534,6 +539,8 @@
             "shaking16",
             "inlining",
             "minification",
+            "minifygeneric",
+            "minifygenericwithinner",
             "assumenosideeffects1",
             "assumenosideeffects2",
             "assumenosideeffects3",
diff --git a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
index 5bae2ed..6f45137 100644
--- a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
@@ -172,11 +172,11 @@
         tmpClassesDir.toString());
     AndroidApp inputApp = ToolHelper.getApp(command);
     assertEquals(1, inputApp.getClasspathResourceProviders().size());
-    assertEquals(tmpClassesDir,
-        ((DirectoryClassFileProvider) inputApp.getClasspathResourceProviders().get(0)).getRoot());
+    assertTrue(Files.isSameFile(tmpClassesDir,
+        ((DirectoryClassFileProvider) inputApp.getClasspathResourceProviders().get(0)).getRoot()));
     assertEquals(1, inputApp.getLibraryResourceProviders().size());
-    assertEquals(tmpClassesDir,
-        ((DirectoryClassFileProvider) inputApp.getLibraryResourceProviders().get(0)).getRoot());
+    assertTrue(Files.isSameFile(tmpClassesDir,
+        ((DirectoryClassFileProvider) inputApp.getLibraryResourceProviders().get(0)).getRoot()));
   }
 
   @Test
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index a207ed8..1dabbd3 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -100,6 +100,7 @@
     return os.path.abspath(f.name)
 
 def main(argv):
+  utils.check_java_version()
   app_provided_pg_conf = False;
   (options, args) = ParseOptions(argv)
   outdir = options.out
diff --git a/tools/run_proguard_dx_on_app.py b/tools/run_proguard_dx_on_app.py
index 8578d1e..92a75ba 100755
--- a/tools/run_proguard_dx_on_app.py
+++ b/tools/run_proguard_dx_on_app.py
@@ -56,6 +56,7 @@
   return parser.parse_args(argv)
 
 def Main(argv):
+  utils.check_java_version()
   options = parse_arguments(argv)
 
   outdir = options.out
diff --git a/tools/test_framework.py b/tools/test_framework.py
index 04a12d3..b328375 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -59,6 +59,7 @@
   return parser.parse_args()
 
 def Main():
+  utils.check_java_version()
   args = parse_arguments()
 
   with utils.TempDir() as temp_dir:
diff --git a/tools/utils.py b/tools/utils.py
index dfc35d7..dab1dd1 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -170,3 +170,18 @@
   for segment_name, size in getDexSegmentSizes(dex_files).items():
     print('{}-{}(CodeSize): {}'
         .format(prefix, segment_name, size))
+
+# ensure that java version is 1.8.*-internal,
+# as opposed to e.g. 1.7* or 1.8.*-google-v7
+def check_java_version():
+  cmd= ['java', '-version']
+  output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
+  m = re.search('openjdk version "([^"]*)"', output)
+  if m is None:
+    raise Exception("Can't check java version: no version string in output"
+        " of 'java -version': '{}'".format(output))
+  version = m.groups(0)[0]
+  m = re.search('1[.]8[.].*-internal', version)
+  if m is None:
+    raise Exception("Incorrect java version, expected: '1.8.*-internal',"
+        " actual: {}".format(version))