Merge "Allow obfuscation of generic signature"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8fa9543..427dd7a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -237,10 +237,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/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/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 58a0c2f..10c89df 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,6 +28,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class ClassNameMinifier {
 
@@ -35,6 +42,11 @@
   private final List<String> dictionary;
   private final boolean keepInnerClassStructure;
 
+  private GenericSignatureRewriter genericSignatureRewriter = new GenericSignatureRewriter();
+
+  private GenericSignatureParser<DexType> genericSignatureParser =
+      new GenericSignatureParser<>(genericSignatureRewriter);
+
   public ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
       List<String> dictionary, boolean keepInnerClassStructure) {
     this.appInfo = appInfo;
@@ -59,11 +71,58 @@
         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);
+      for (DexEncodedField field : clazz.staticFields) {
+        rewriteGenericSignatures(field.annotations.annotations,
+            genericSignatureParser::parseFieldSignature);
+      }
+      for (DexEncodedField field : clazz.instanceFields) {
+        rewriteGenericSignatures(field.annotations.annotations,
+            genericSignatureParser::parseFieldSignature);
+      }
+      for (DexEncodedMethod method : clazz.directMethods) {
+        rewriteGenericSignatures(method.annotations.annotations,
+            genericSignatureParser::parseMethodSignature);
+      }
+      for (DexEncodedMethod method : clazz.virtualMethods) {
+        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>
@@ -209,4 +268,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/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/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 3487694..55a9fc0 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",