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",