Merge "Restrict vertical class merging"
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 043af39..01fff01 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -161,7 +161,7 @@
}
private void parseError(DexItem item, Origin origin, GenericSignatureFormatError e) {
- StringBuilder message = new StringBuilder("Invalid class signature for ");
+ StringBuilder message = new StringBuilder("Invalid signature for ");
if (item instanceof DexClass) {
message.append("class ");
message.append(((DexClass) item).getType().toSourceString());
@@ -183,12 +183,14 @@
clazz.annotations = rewriteGenericSignatures(clazz.annotations,
genericSignatureParser::parseClassSignature,
e -> parseError(clazz, clazz.getOrigin(), e));
- clazz.forEachField(field -> rewriteGenericSignatures(
- field.annotations, genericSignatureParser::parseFieldSignature,
- e -> parseError(field, clazz.getOrigin(), e)));
- clazz.forEachMethod(method -> rewriteGenericSignatures(
- method.annotations, genericSignatureParser::parseMethodSignature,
- e -> parseError(method, clazz.getOrigin(), e)));
+ clazz.forEachField(field ->
+ field.annotations = rewriteGenericSignatures(
+ field.annotations, genericSignatureParser::parseFieldSignature,
+ e -> parseError(field, clazz.getOrigin(), e)));
+ clazz.forEachMethod(method ->
+ method.annotations = rewriteGenericSignatures(
+ method.annotations, genericSignatureParser::parseMethodSignature,
+ e -> parseError(method, clazz.getOrigin(), e)));
}
}
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
index 5b549a3..c1f6a7d 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -84,7 +84,7 @@
throw e;
} catch (Throwable t) {
Error e = new GenericSignatureFormatError(
- "Unknown error parsing generic signature: " + t.getMessage());
+ "Unknown error parsing class signature: " + t.getMessage());
e.addSuppressed(t);
throw e;
}
@@ -100,7 +100,7 @@
throw e;
} catch (Throwable t) {
Error e = new GenericSignatureFormatError(
- "Unknown error parsing generic signature: " + t.getMessage());
+ "Unknown error parsing method signature: " + t.getMessage());
e.addSuppressed(t);
throw e;
}
@@ -112,14 +112,14 @@
setInput(signature);
parseFieldTypeSignature();
actions.stop();
- } catch (GenericSignatureFormatError e) {
- throw e;
- } catch (Throwable t) {
- Error e = new GenericSignatureFormatError(
- "Unknown error parsing generic signature: " + t.getMessage());
- e.addSuppressed(t);
- throw e;
- }
+ } catch (GenericSignatureFormatError e) {
+ throw e;
+ } catch (Throwable t) {
+ Error e = new GenericSignatureFormatError(
+ "Unknown error parsing field signature: " + t.getMessage());
+ e.addSuppressed(t);
+ throw e;
+ }
}
private void setInput(String input) {
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 5847a80..ebf110e 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
import com.google.common.collect.Sets;
@@ -28,7 +29,8 @@
}
private void identifyBridgeMethod(DexEncodedMethod method) {
- if (method.accessFlags.isBridge()) {
+ MethodAccessFlags accessFlags = method.accessFlags;
+ if (accessFlags.isBridge() && !accessFlags.isAbstract()) {
InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
method.getCode().registerCodeReferences(targetExtractor);
DexMethod target = targetExtractor.getTarget();
@@ -36,11 +38,11 @@
// javac-generated visibility forward bridge method has same descriptor (name, signature and
// return type).
if (target != null && target.hasSameProtoAndName(method.method)) {
- assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
+ assert !accessFlags.isPrivate() && !accessFlags.isConstructor();
if (kind == InvokeKind.SUPER) {
// This is a visibility forward, so check for the direct target.
- DexEncodedMethod targetMethod
- = appInfo.resolveMethod(target.getHolder(), target).asSingleTarget();
+ DexEncodedMethod targetMethod =
+ appInfo.resolveMethod(target.getHolder(), target).asSingleTarget();
if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
if (Log.ENABLED) {
Log.info(getClass(), "Removing visibility forwarding %s -> %s", method.method,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 000076e..d5d0558 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -807,7 +807,9 @@
public static R8Command.Builder prepareR8CommandBuilder(
AndroidApp app, ProgramConsumer programConsumer) {
- return R8Command.builder(app).setProgramConsumer(programConsumer);
+ return R8Command.builder(app)
+ .setProgramConsumer(programConsumer)
+ .setProguardMapConsumer(StringConsumer.emptyConsumer());
}
public static AndroidApp runR8(AndroidApp app) throws IOException {
@@ -1413,6 +1415,11 @@
return result.stdout;
}
+ public static ProcessResult runProguardRaw(
+ Path inJar, Path outJar, Path lib, Path config, Path map) throws IOException {
+ return runProguardRaw(getProguardScript(), inJar, outJar, lib, ImmutableList.of(config), map);
+ }
+
public static ProcessResult runProguardRaw(Path inJar, Path outJar, List<Path> config, Path map)
throws IOException {
return runProguardRaw(getProguardScript(), inJar, outJar, config, map);
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
new file mode 100644
index 0000000..160ad59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2018, 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.bridgeremoval;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class EmptyBridgeTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder abs = jasminBuilder.addClass("Abs");
+ abs.setAccess("public abstract");
+ abs.addMethod("public bridge abstract", "emptyBridge", ImmutableList.of(), "V");
+
+ ClassBuilder cls = jasminBuilder.addClass("Main");
+ cls.addVirtualMethod("bogus", ImmutableList.of(), "V",
+ ".limit stack 3",
+ ".limit locals 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "ldc \"foo\"",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+ cls.addMainMethod(
+ ".limit stack 4",
+ ".limit locals 2",
+ "new " + cls.name,
+ "dup",
+ "invokespecial " + cls.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "invokevirtual " + cls.name + "/bogus()V",
+ "return");
+
+ String absClassName = abs.name;
+ String mainClassName = cls.name;
+ String proguardConfig =
+ "-allowaccessmodification" + System.lineSeparator()
+ + "-keep class " + mainClassName + "{ *; }" + System.lineSeparator()
+ + "-keep class " + absClassName + "{ *; }";
+ AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig);
+
+ DexInspector inspector = new DexInspector(processedApp);
+ ClassSubject classSubject = inspector.clazz(absClassName);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method("void", "emptyBridge", ImmutableList.of());
+ assertThat(methodSubject, isPresent());
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
new file mode 100644
index 0000000..2c1479e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, 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.graph;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokespecial.Main;
+import com.android.tools.r8.graph.invokespecial.TestClassDump;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InvokeSpecialTest extends AsmTestBase {
+
+ @Ignore("b/110175213")
+ @Test
+ public void testInvokeSpecial() throws Exception {
+ ensureSameOutput(
+ Main.class.getCanonicalName(),
+ ToolHelper.getClassAsBytes(Main.class),
+ TestClassDump.dump());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
new file mode 100644
index 0000000..deb316d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, 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.graph.invokespecial;
+
+public class Main {
+
+ public static void main(String[] args) {
+ TestClass x = new TestClass();
+ x.m(true);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
new file mode 100644
index 0000000..c08091e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, 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.graph.invokespecial;
+
+public class TestClass {
+
+ public void m(boolean recurse) {
+ System.out.println(recurse);
+ if (recurse) {
+ m(false);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
new file mode 100644
index 0000000..ae336834
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2018, 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.graph.invokespecial;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running ./tools/asmifier.py build/classes/test/com/android/tools/r8/graph/-
+// invokespecial/TestClass.class, and changing the invoke-virtual TestClass.m() instruction to
+// an invoke-special instruction.
+public class TestClassDump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/graph/invokespecial/TestClass",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "m", "(Z)V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
+ mv.visitVarInsn(ILOAD, 1);
+ Label l0 = new Label();
+ mv.visitJumpInsn(IFEQ, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ICONST_0);
+ // Note: Changed from INVOKEVIRTUAL to INVOKESPECIAL.
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "com/android/tools/r8/graph/invokespecial/TestClass", "m", "(Z)V", false);
+ mv.visitLabel(l0);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index eb6e943..ba04454 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -195,7 +195,7 @@
return addStaticMethod("main", ImmutableList.of("[Ljava/lang/String;"), "V", lines);
}
- private MethodSignature addMethod(
+ public MethodSignature addMethod(
String access,
String name,
List<String> argumentTypes,
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
index 1d1604b..957bc1d 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -350,7 +350,8 @@
forMethodSignatures(this::parseMethodSignature);
}
- private void failingParseAction(Consumer<GenericSignatureParser<String>> parse)
+ private void failingParseAction(
+ Consumer<GenericSignatureParser<String>> parse, String errorMessageType)
throws Exception {
class ThrowsInParserActionBase<E extends Error> extends ReGenerateGenericSignatureRewriter {
protected Supplier<? extends E> exceptionSupplier;
@@ -452,7 +453,8 @@
assertEquals("ERROR", e.getMessage());
} else {
plainErrorCount++;
- assertEquals("Unknown error parsing generic signature: ERROR", e.getMessage());
+ assertEquals("Unknown error parsing "
+ + errorMessageType + " signature: ERROR", e.getMessage());
}
}
}
@@ -463,10 +465,10 @@
public void failingParseAction() throws Exception {
// These signatures hits all action callbacks.
failingParseAction(parser -> parser.parseClassSignature(
- "<U:Ljava/lang/Object;>LOuter<TT;>.Inner;Ljava/util/List<TU;>;"));
+ "<U:Ljava/lang/Object;>LOuter<TT;>.Inner;Ljava/util/List<TU;>;"), "class");
failingParseAction(
- parser -> parser.parseFieldSignature("LOuter$InnerInterface<TU;>.Inner;"));
+ parser -> parser.parseFieldSignature("LOuter$InnerInterface<TU;>.Inner;"), "field");
failingParseAction(
- parser -> parser.parseMethodSignature("(LOuter$InnerInterface<TU;>.Inner;)V"));
+ parser -> parser.parseMethodSignature("(LOuter$InnerInterface<TU;>.Inner;)V"), "method");
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index fc0436d..b9ff7ea 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -477,7 +477,7 @@
testSingleClass("Outer", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid class signature for class Outer", "Expected L at position 1");
+ "Invalid signature for class Outer", "Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
}
@@ -486,7 +486,7 @@
testSingleClass("Outer", "<L", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid class signature for class Outer", "Unexpected end of signature at position 3");
+ "Invalid signature for class Outer", "Unexpected end of signature at position 3");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
}
@@ -495,7 +495,7 @@
testSingleClass("Outer$ExtendsInner", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid class signature for class Outer$ExtendsInner", "Expected L at position 1");
+ "Invalid signature for class Outer$ExtendsInner", "Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
}
@@ -504,7 +504,7 @@
testSingleClass("Outer$Inner$ExtendsInnerInner", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid class signature for class Outer$Inner$ExtendsInnerInner",
+ "Invalid signature for class Outer$Inner$ExtendsInnerInner",
"Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
}
@@ -528,7 +528,7 @@
testSingleClass("Outer$ExtendsInner", signature, diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid class signature for class Outer$ExtendsInner", "Expected ; at position 16");
+ "Invalid signature for class Outer$ExtendsInner", "Expected ; at position 16");
}, inspector -> {
noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
});
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
new file mode 100644
index 0000000..e48ce56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -0,0 +1,289 @@
+// Copyright (c) 2018, 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;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.PUTFIELD;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+
+public class MinifierFieldSignatureTest extends TestBase {
+ /*
+
+ class Fields<X extends String> {
+ class Inner {
+ }
+ public X anX;
+ public X[] anArrayOfX;
+ public Fields<X> aFieldsOfX;
+ public Fields<X>.Inner aFieldsOfXInner;
+ }
+
+ */
+
+ private String anXSignature = "TX;";
+ private String anArrayOfXSignature = "[TX;";
+ private String aFieldsOfXSignature = "LFields<TX;>;";
+ private String aFieldsOfXInnerSignature = "LFields<TX;>.Inner;";
+
+ public byte[] dumpFields(Map<String, String> signatures) throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ String signature;
+
+ cw.visit(V1_8, ACC_SUPER, "Fields", "<X:Ljava/lang/String;>Ljava/lang/Object;",
+ "java/lang/Object", null);
+
+ cw.visitInnerClass("Fields$Inner", "Fields", "Inner", 0);
+
+ {
+ signature = signatures.get("anX");
+ signature = signature == null ? anXSignature : signature;
+ fv = cw.visitField(ACC_PUBLIC, "anX", "Ljava/lang/String;", signature, null);
+ fv.visitEnd();
+ }
+ {
+ signature = signatures.get("anArrayOfX");
+ signature = signature == null ? anArrayOfXSignature : signature;
+ fv = cw.visitField(
+ ACC_PUBLIC, "anArrayOfX", "[Ljava/lang/String;", signature, null);
+ fv.visitEnd();
+ }
+ {
+ signature = signatures.get("aFieldsOfX");
+ signature = signature == null ? aFieldsOfXSignature : signature;
+ fv = cw.visitField(ACC_PUBLIC, "aFieldsOfX", "LFields;", signature, null);
+ fv.visitEnd();
+ }
+ {
+ signature = signatures.get("aFieldsOfXInner");
+ signature = signature == null ? aFieldsOfXInnerSignature : signature;
+ fv = cw.visitField(
+ ACC_PUBLIC, "aFieldsOfXInner", "LFields$Inner;", signature, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public byte[] dumpInner() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+
+ cw.visit(V1_8, ACC_SUPER, "Fields$Inner", null, "java/lang/Object", null);
+
+ cw.visitInnerClass("Fields$Inner", "Fields", "Inner", 0);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LFields;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "(LFields;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "Fields$Inner", "this$0", "LFields;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private FieldSubject lookupAnX(DexInspector inspector) {
+ ClassSubject clazz = inspector.clazz("Fields");
+ return clazz.field("java.lang.String", "anX");
+ }
+
+ private FieldSubject lookupAnArrayOfX(DexInspector inspector) {
+ ClassSubject clazz = inspector.clazz("Fields");
+ return clazz.field("java.lang.String[]", "anArrayOfX");
+ }
+
+ private FieldSubject lookupAFieldsOfX(DexInspector inspector) {
+ ClassSubject clazz = inspector.clazz("Fields");
+ return clazz.field("Fields", "aFieldsOfX");
+ }
+
+ public void runTest(
+ ImmutableMap<String, String> signatures,
+ Consumer<DiagnosticsChecker> diagnostics,
+ Consumer<DexInspector> inspect)
+ throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ DexInspector inspector = new DexInspector(
+ ToolHelper.runR8(R8Command.builder(checker)
+ .addClassProgramData(dumpFields(signatures), Origin.unknown())
+ .addClassProgramData(dumpInner(), Origin.unknown())
+ .addProguardConfiguration(ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class ** { *; }"
+ ), Origin.unknown())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build()));
+ // All classes are kept, and renamed.
+ ClassSubject clazz = inspector.clazz("Fields");
+ assertThat(clazz, isRenamed());
+ assertThat(inspector.clazz("Fields$Inner"), isRenamed());
+
+ FieldSubject anX = lookupAnX(inspector);
+ FieldSubject anArrayOfX = lookupAnArrayOfX(inspector);
+ FieldSubject aFieldsOfX =lookupAFieldsOfX(inspector);
+ FieldSubject aFieldsOfXInner = clazz.field("Fields$Inner", "aFieldsOfXInner");
+
+ // Check that all fields have been renamed
+ assertThat(anX, isRenamed());
+ assertThat(anArrayOfX, isRenamed());
+ assertThat(aFieldsOfX, isRenamed());
+ assertThat(aFieldsOfXInner, isRenamed());
+
+ //System.out.println(generic.getFinalSignatureAttribute());
+ //System.out.println(generic.getOriginalSignatureAttribute());
+
+ // Test that methods have their original signature if the default was provided.
+ if (!signatures.containsKey("anX")) {
+ assertEquals(anXSignature, anX.getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("anArrayOfX")) {
+ assertEquals(anArrayOfXSignature, anArrayOfX.getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("aFieldsOfX")) {
+ assertEquals(
+ aFieldsOfXSignature, aFieldsOfX.getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("aFieldsOfXInner")) {
+ assertEquals(
+ aFieldsOfXInnerSignature, aFieldsOfXInner.getOriginalSignatureAttribute());
+ }
+
+ diagnostics.accept(checker);
+ inspect.accept(inspector);
+ }
+
+ private void testSingleField(String name, String signature,
+ Consumer<DiagnosticsChecker> diagnostics,
+ Consumer<DexInspector> inspector)
+ throws Exception {
+ ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
+ runTest(signatures, diagnostics, inspector);
+ }
+
+ private void isOriginUnknown(Origin origin) {
+ assertSame(Origin.unknown(), origin);
+ }
+
+ private void noWarnings(DiagnosticsChecker checker) {
+ assertEquals(0, checker.warnings.size());
+ }
+
+ private void noInspection(DexInspector inspector) {
+ }
+
+ private void noSignatureAttribute(FieldSubject field) {
+ assertThat(field, isPresent());
+ assertNull(field.getFinalSignatureAttribute());
+ assertNull(field.getOriginalSignatureAttribute());
+ }
+
+ @Test
+ public void originalJavacSignatures() throws Exception {
+ // Test using the signatures generated by javac.
+ runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+ }
+
+ @Test
+ public void signatureEmpty() throws Exception {
+ testSingleField("anX", "", this::noWarnings,
+ inspector -> noSignatureAttribute(lookupAnX(inspector)));
+ }
+
+ @Test
+ public void signatureInvalid() throws Exception {
+ testSingleField("anX", "X", diagnostics -> {
+ assertEquals(1, diagnostics.warnings.size());
+ // TODO(sgjesse): The position 2 reported here is one off.
+ DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+ "Invalid signature for field",
+ "java.lang.String Fields.anX",
+ "Expected L, [ or T at position 2");
+ }, inspector -> noSignatureAttribute(lookupAnX(inspector)));
+ }
+
+ @Test
+ public void classNotFound() throws Exception {
+ String signature = "LNotFound<TX;>.InnerNotFound.InnerAlsoNotFound;";
+ testSingleField("anX", signature, this::noWarnings, inspector -> {
+ assertThat(inspector.clazz("NotFound"), not(isPresent()));
+ assertEquals(signature, lookupAnX(inspector).getOriginalSignatureAttribute());
+ });
+ }
+
+ @Test
+ public void multipleWarnings() throws Exception {
+ runTest(ImmutableMap.of(
+ "anX", "X",
+ "anArrayOfX", "X",
+ "aFieldsOfX", "X"
+ ), diagnostics -> {
+ assertEquals(3, diagnostics.warnings.size());
+ }, inspector -> {
+ noSignatureAttribute(lookupAnX(inspector));
+ noSignatureAttribute(lookupAnArrayOfX(inspector));
+ noSignatureAttribute(lookupAFieldsOfX(inspector));
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
new file mode 100644
index 0000000..41750ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -0,0 +1,314 @@
+// Copyright (c) 2018, 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;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.PUTFIELD;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+public class MinifierMethodSignatureTest extends TestBase {
+ /*
+
+ class Methods<X extends Throwable> {
+ class Inner {
+ }
+ public static <T extends Throwable> T generic(T a, Methods<T>.Inner b) { return null; }
+ public Methods<X>.Inner parameterizedReturn() { return null; }
+ public void parameterizedArguments(X a, Methods<X>.Inner b) { }
+ public void parametrizedThrows() throws X { }
+ }
+
+ */
+
+ private String genericSignature = "<T:Ljava/lang/Throwable;>(TT;LMethods<TT;>.Inner;)TT;";
+ private String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
+ private String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
+ private String parametrizedThrowsSignature = "()V^TX;";
+
+ private byte[] dumpMethods(Map<String, String> signatures) throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+ String signature;
+
+ cw.visit(V1_8, ACC_SUPER, "Methods", "<X:Ljava/lang/Throwable;>Ljava/lang/Object;",
+ "java/lang/Object", null);
+
+ cw.visitInnerClass("Methods$Inner", "Methods", "Inner", 0);
+
+ {
+ mv = cw.visitMethod(0, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ signature = signatures.get("generic");
+ signature = signature == null ? genericSignature : signature;
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "generic",
+ "(Ljava/lang/Throwable;LMethods$Inner;)Ljava/lang/Throwable;",
+ signature, null);
+ mv.visitCode();
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ signature = signatures.get("parameterizedReturn");
+ signature = signature == null ? parameterizedReturnSignature : signature;
+ mv = cw.visitMethod(ACC_PUBLIC, "parameterizedReturn", "()LMethods$Inner;",
+ signature, null);
+ mv.visitCode();
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ signature = signatures.get("parameterizedArguments");
+ signature = signature == null ? parameterizedArgumentsSignature : signature;
+ mv = cw.visitMethod(ACC_PUBLIC, "parameterizedArguments",
+ "(Ljava/lang/Throwable;LMethods$Inner;)V", signature, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 3);
+ mv.visitEnd();
+ }
+ {
+ signature = signatures.get("parametrizedThrows");
+ signature = signature == null ? parametrizedThrowsSignature : signature;
+ mv = cw.visitMethod(ACC_PUBLIC, "parametrizedThrows", "()V", signature,
+ new String[] { "java/lang/Throwable" });
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private byte[] dumpInner() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+
+ cw.visit(V1_8, ACC_SUPER, "Methods$Inner", null, "java/lang/Object", null);
+
+ cw.visitInnerClass("Methods$Inner", "Methods", "Inner", 0);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LMethods;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "(LMethods;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "Methods$Inner", "this$0", "LMethods;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private MethodSubject lookupGeneric(DexInspector inspector) {
+ ClassSubject clazz = inspector.clazz("Methods");
+ return clazz.method(
+ "java.lang.Throwable", "generic", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
+ }
+
+ private MethodSubject lookupParameterizedReturn(DexInspector inspector) {
+ ClassSubject clazz = inspector.clazz("Methods");
+ return clazz.method(
+ "Methods$Inner", "parameterizedReturn", ImmutableList.of());
+ }
+
+ private MethodSubject lookupParameterizedArguments(DexInspector inspector) {
+ ClassSubject clazz = inspector.clazz("Methods");
+ return clazz.method(
+ "void", "parameterizedArguments", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
+ }
+
+ public void runTest(
+ ImmutableMap<String, String> signatures,
+ Consumer<DiagnosticsChecker> diagnostics,
+ Consumer<DexInspector> inspect)
+ throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ DexInspector inspector = new DexInspector(
+ ToolHelper.runR8(R8Command.builder(checker)
+ .addClassProgramData(dumpMethods(signatures), Origin.unknown())
+ .addClassProgramData(dumpInner(), Origin.unknown())
+ .addProguardConfiguration(ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class ** { *; }"
+ ), Origin.unknown())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build()));
+ // All classes are kept, and renamed.
+ ClassSubject clazz = inspector.clazz("Methods");
+ assertThat(clazz, isRenamed());
+ assertThat(inspector.clazz("Methods$Inner"), isRenamed());
+
+ MethodSubject generic = lookupGeneric(inspector);
+ MethodSubject parameterizedReturn = lookupParameterizedReturn(inspector);
+ MethodSubject parameterizedArguments = lookupParameterizedArguments(inspector);
+ MethodSubject parametrizedThrows =
+ clazz.method("void", "parametrizedThrows", ImmutableList.of());
+
+ // Check that all methods have been renamed
+ assertThat(generic, isRenamed());
+ assertThat(parameterizedReturn, isRenamed());
+ assertThat(parameterizedArguments, isRenamed());
+ assertThat(parametrizedThrows, isRenamed());
+
+ // Test that methods have their original signature if the default was provided.
+ if (!signatures.containsKey("generic")) {
+ assertEquals(genericSignature, generic.getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("parameterizedReturn")) {
+ assertEquals(
+ parameterizedReturnSignature, parameterizedReturn.getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("parameterizedArguments")) {
+ assertEquals(
+ parameterizedArgumentsSignature, parameterizedArguments.getOriginalSignatureAttribute());
+ }
+ if (!signatures.containsKey("parametrizedThrows")) {
+ assertEquals(
+ parametrizedThrowsSignature, parametrizedThrows.getOriginalSignatureAttribute());
+ }
+
+ diagnostics.accept(checker);
+ inspect.accept(inspector);
+ }
+
+ private void testSingleMethod(String name, String signature,
+ Consumer<DiagnosticsChecker> diagnostics,
+ Consumer<DexInspector> inspector)
+ throws Exception {
+ ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
+ runTest(signatures, diagnostics, inspector);
+ }
+
+ private void isOriginUnknown(Origin origin) {
+ assertSame(Origin.unknown(), origin);
+ }
+
+ private void noWarnings(DiagnosticsChecker checker) {
+ assertEquals(0, checker.warnings.size());
+ }
+
+ private void noInspection(DexInspector inspector) {
+ }
+
+ private void noSignatureAttribute(MethodSubject method) {
+ assertThat(method, isPresent());
+ assertNull(method.getFinalSignatureAttribute());
+ assertNull(method.getOriginalSignatureAttribute());
+ }
+
+ @Test
+ public void originalJavacSignatures() throws Exception {
+ // Test using the signatures generated by javac.
+ runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+ }
+
+ @Test
+ public void signatureEmpty() throws Exception {
+ testSingleMethod("generic", "", this::noWarnings, inspector -> {
+ noSignatureAttribute(lookupGeneric(inspector));
+ });
+ }
+
+ @Test
+ public void signatureInvalid() throws Exception {
+ testSingleMethod("generic", "X", diagnostics -> {
+ assertEquals(1, diagnostics.warnings.size());
+ DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+ "Invalid signature for method",
+ "java.lang.Throwable Methods.generic(java.lang.Throwable, Methods$Inner)",
+ "Expected ( at position 1");
+ }, inspector -> noSignatureAttribute(lookupGeneric(inspector)));
+ }
+
+ @Test
+ public void classNotFound() throws Exception {
+ String signature = "<T:LNotFound;>(TT;LAlsoNotFound<TT;>.InnerNotFound.InnerAlsoNotFound;)TT;";
+ testSingleMethod("generic", signature, this::noWarnings,
+ inspector -> {
+ ClassSubject methods = inspector.clazz("Methods");
+ MethodSubject method =
+ methods.method("java.lang.Throwable", "generic",
+ ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
+ assertThat(inspector.clazz("NotFound"), not(isPresent()));
+ assertEquals(signature, method.getOriginalSignatureAttribute());
+ });
+ }
+
+ @Test
+ public void multipleWarnings() throws Exception {
+ runTest(ImmutableMap.of(
+ "generic", "X",
+ "parameterizedReturn", "X",
+ "parameterizedArguments", "X"
+ ), diagnostics -> {
+ assertEquals(3, diagnostics.warnings.size());
+ }, inspector -> {
+ noSignatureAttribute(lookupGeneric(inspector));
+ noSignatureAttribute(lookupParameterizedReturn(inspector));
+ noSignatureAttribute(lookupParameterizedArguments(inspector));
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index c82bbc9..9a9ca34 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -4,52 +4,303 @@
package com.android.tools.r8.naming.b72391662;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageSuper;
import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(VmTestRunner.class)
-public class B72391662 extends TestBase {
+public class B72391662 extends ProguardCompatabilityTestBase {
- private void doTest(boolean allowAccessModification, boolean minify) throws Exception {
+ private static final List<Class> CLASSES = ImmutableList.of(
+ TestMain.class, Interface.class, Super.class, TestClass.class,
+ OtherPackageSuper.class, OtherPackageTestClass.class);
+
+ private void doTest_keepAll(
+ Shrinker shrinker,
+ String repackagePrefix,
+ boolean allowAccessModification,
+ boolean minify) throws Exception {
Class mainClass = TestMain.class;
+ String keep = !minify ? "-keep" : "-keep,allowobfuscation";
List<String> config = ImmutableList.of(
- allowAccessModification ?"-allowaccessmodification" : "",
+ "-printmapping",
+ repackagePrefix != null ? "-repackageclasses '" + repackagePrefix + "'" : "",
+ allowAccessModification ? "-allowaccessmodification" : "",
!minify ? "-dontobfuscate" : "",
"-keep class " + mainClass.getCanonicalName() + " {",
" public void main(java.lang.String[]);",
"}",
- "-keep class " + TestClass.class.getCanonicalName() + " {",
+ keep + " class " + TestClass.class.getCanonicalName() + " {",
" *;",
"}",
- "-keep class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+ keep + " class " + OtherPackageTestClass.class.getCanonicalName() + " {",
" *;",
- "}"
+ "}",
+ "-dontwarn java.lang.invoke.*"
);
- AndroidApp app = readClassesAndAndriodJar(ImmutableList.of(
- mainClass, Interface.class, Super.class, TestClass.class,
- OtherPackageSuper.class, OtherPackageTestClass.class));
- app = compileWithR8(app, String.join(System.lineSeparator(), config));
+ AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+
+ DexInspector dexInspector =
+ isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
+ ClassSubject testClass = dexInspector.clazz(TestClass.class);
+ assertThat(testClass, isPresent());
+
+ // Test the totally unused method.
+ MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+ assertThat(staticMethod, isPresent());
+ assertEquals(minify, staticMethod.isRenamed());
+ if (isR8(shrinker)) {
+ assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic());
+ } else {
+ assertFalse(staticMethod.getMethod().accessFlags.isPublic());
+ }
+
+ // Test an indirectly referred method.
+ staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+ assertThat(staticMethod, isPresent());
+ assertEquals(minify, staticMethod.isRenamed());
+ boolean publicizeCondition = isR8(shrinker) ? allowAccessModification
+ : minify && repackagePrefix != null && allowAccessModification;
+ assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
}
@Test
@IgnoreIfVmOlderThan(Version.V7_0_0)
- public void test() throws Exception {
- doTest(true, true);
- doTest(true, false);
- doTest(false, true);
- doTest(false, false);
+ public void test_keepAll_R8() throws Exception {
+ doTest_keepAll(Shrinker.R8, "r8", true, true);
+ doTest_keepAll(Shrinker.R8, "r8", true, false);
+ doTest_keepAll(Shrinker.R8, "r8", false, true);
+ doTest_keepAll(Shrinker.R8, "r8", false, false);
+ doTest_keepAll(Shrinker.R8, null, true, true);
+ doTest_keepAll(Shrinker.R8, null, true, false);
+ doTest_keepAll(Shrinker.R8, null, false, true);
+ doTest_keepAll(Shrinker.R8, null, false, false);
+ }
+
+ @Ignore("b/92236970")
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepAll_R8Compat() throws Exception {
+ doTest_keepAll(Shrinker.R8_COMPAT, "rc", true, true);
+ doTest_keepAll(Shrinker.R8_COMPAT, "rc", true, false);
+ doTest_keepAll(Shrinker.R8_COMPAT, "rc", false, true);
+ doTest_keepAll(Shrinker.R8_COMPAT, "rc", false, false);
+ doTest_keepAll(Shrinker.R8_COMPAT, null, true, true);
+ doTest_keepAll(Shrinker.R8_COMPAT, null, true, false);
+ doTest_keepAll(Shrinker.R8_COMPAT, null, false, true);
+ doTest_keepAll(Shrinker.R8_COMPAT, null, false, false);
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepAll_Proguard6() throws Exception {
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", true, true);
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", true, false);
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", false, true);
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", false, false);
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, true, true);
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, true, false);
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, false, true);
+ doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, false, false);
+ }
+
+ private void doTest_keepNonPublic(
+ Shrinker shrinker,
+ String repackagePrefix,
+ boolean allowAccessModification,
+ boolean minify) throws Exception {
+ Class mainClass = TestMain.class;
+ String keep = !minify ? "-keep" : "-keep,allowobfuscation";
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ repackagePrefix != null ? "-repackageclasses '" + repackagePrefix + "'" : "",
+ allowAccessModification ? "-allowaccessmodification" : "",
+ !minify ? "-dontobfuscate" : "",
+ "-keep class " + mainClass.getCanonicalName() + " {",
+ " public void main(java.lang.String[]);",
+ "}",
+ keep + " class " + TestClass.class.getCanonicalName() + " {",
+ " !public <methods>;",
+ "}",
+ keep + " class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+ " !public <methods>;",
+ "}",
+ "-dontwarn java.lang.invoke.*"
+ );
+
+ AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+ assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+
+ DexInspector dexInspector =
+ isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
+ ClassSubject testClass = dexInspector.clazz(TestClass.class);
+ assertThat(testClass, isPresent());
+
+ // Test the totally unused method.
+ MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+ assertThat(staticMethod, isPresent());
+ assertEquals(minify, staticMethod.isRenamed());
+ if (isR8(shrinker)) {
+ assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic());
+ } else {
+ assertFalse(staticMethod.getMethod().accessFlags.isPublic());
+ }
+
+ // Test an indirectly referred method.
+ staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+ assertThat(staticMethod, isPresent());
+ assertEquals(minify, staticMethod.isRenamed());
+ boolean publicizeCondition = isR8(shrinker) ? allowAccessModification
+ : minify && repackagePrefix != null && allowAccessModification;
+ assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepNonPublic_R8() throws Exception {
+ doTest_keepNonPublic(Shrinker.R8, "r8", true, true);
+ doTest_keepNonPublic(Shrinker.R8, "r8", true, false);
+ doTest_keepNonPublic(Shrinker.R8, "r8", false, true);
+ doTest_keepNonPublic(Shrinker.R8, "r8", false, false);
+ doTest_keepNonPublic(Shrinker.R8, null, true, true);
+ doTest_keepNonPublic(Shrinker.R8, null, true, false);
+ doTest_keepNonPublic(Shrinker.R8, null, false, true);
+ doTest_keepNonPublic(Shrinker.R8, null, false, false);
+ }
+
+ @Ignore("b/92236970")
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepNonPublic_R8Compat() throws Exception {
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", true, true);
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", true, false);
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", false, true);
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", false, false);
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, null, true, true);
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, null, true, false);
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, null, false, true);
+ doTest_keepNonPublic(Shrinker.R8_COMPAT, null, false, false);
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepNonPublic_Proguard6() throws Exception {
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, true);
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, false);
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, true);
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, false);
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, true, true);
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, true, false);
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, false, true);
+ doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, false, false);
+ }
+
+ private void doTest_keepPublic(
+ Shrinker shrinker,
+ String repackagePrefix,
+ boolean allowAccessModification,
+ boolean minify) throws Exception {
+ Class mainClass = TestMain.class;
+ String keep = !minify ? "-keep" : "-keep,allowobfuscation";
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ repackagePrefix != null ? "-repackageclasses '" + repackagePrefix + "'" : "",
+ allowAccessModification ? "-allowaccessmodification" : "",
+ !minify ? "-dontobfuscate" : "",
+ "-keep class " + mainClass.getCanonicalName() + " {",
+ " public void main(java.lang.String[]);",
+ "}",
+ keep + " class " + TestClass.class.getCanonicalName() + " {",
+ " public <methods>;",
+ "}",
+ keep + " class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+ " public <methods>;",
+ "}",
+ "-dontwarn java.lang.invoke.*"
+ );
+
+ AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+ assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+
+ DexInspector dexInspector =
+ isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
+ ClassSubject testClass = dexInspector.clazz(TestClass.class);
+ assertThat(testClass, isPresent());
+
+ // Test the totally unused method.
+ MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+ assertThat(staticMethod, not(isPresent()));
+
+ // Test an indirectly referred method.
+ staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+ if (isR8(shrinker)) {
+ // Inlined.
+ assertThat(staticMethod, not(isPresent()));
+ } else {
+ assertThat(staticMethod, isPresent());
+ assertEquals(minify, staticMethod.isRenamed());
+ boolean publicizeCondition = minify && repackagePrefix != null && allowAccessModification;
+ assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
+ }
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepPublic_R8() throws Exception {
+ doTest_keepPublic(Shrinker.R8, "r8", true, true);
+ doTest_keepPublic(Shrinker.R8, "r8", true, false);
+ doTest_keepPublic(Shrinker.R8, "r8", false, true);
+ doTest_keepPublic(Shrinker.R8, "r8", false, false);
+ doTest_keepPublic(Shrinker.R8, null, true, true);
+ doTest_keepPublic(Shrinker.R8, null, true, false);
+ doTest_keepPublic(Shrinker.R8, null, false, true);
+ doTest_keepPublic(Shrinker.R8, null, false, false);
+ }
+
+ @Ignore("b/92236970")
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepPublic_R8Compat() throws Exception {
+ doTest_keepPublic(Shrinker.R8_COMPAT, "rc", true, true);
+ doTest_keepPublic(Shrinker.R8_COMPAT, "rc", true, false);
+ doTest_keepPublic(Shrinker.R8_COMPAT, "rc", false, true);
+ doTest_keepPublic(Shrinker.R8_COMPAT, "rc", false, false);
+ doTest_keepPublic(Shrinker.R8_COMPAT, null, true, true);
+ doTest_keepPublic(Shrinker.R8_COMPAT, null, true, false);
+ doTest_keepPublic(Shrinker.R8_COMPAT, null, false, true);
+ doTest_keepPublic(Shrinker.R8_COMPAT, null, false, false);
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test_keepPublic_Proguard6() throws Exception {
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, true);
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, false);
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, true);
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, false);
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, true, true);
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, true, false);
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, false, true);
+ doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, false, false);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
index 88efc09..f13700c 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
@@ -18,6 +18,10 @@
return value;
}
+ static void unused() {
+ System.out.println("This should be discarded unless there is a keep rule.");
+ }
+
static String staticMethod() {
return "1";
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
index 3976f89..10c64e3 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -6,24 +6,31 @@
import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
import com.android.tools.r8.CompatProguardCommandBuilder;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
+import java.util.function.Consumer;
public class ProguardCompatabilityTestBase extends TestBase {
+ protected Path proguardMap;
+
public enum Shrinker {
PROGUARD5,
PROGUARD6,
@@ -32,6 +39,34 @@
R8
}
+ protected static boolean isR8(Shrinker shrinker) {
+ return shrinker == Shrinker.R8_COMPAT || shrinker == Shrinker.R8;
+ }
+
+ protected AndroidApp runShrinkerRaw(
+ Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
+ return runShrinkerRaw(
+ mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
+ }
+
+ protected AndroidApp runShrinkerRaw(
+ Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ switch (mode) {
+ case PROGUARD5:
+ return runProguard5Raw(programClasses, proguardConfig, proguardMap);
+ case PROGUARD6:
+ return runProguard6Raw(programClasses, proguardConfig, proguardMap);
+ case PROGUARD6_THEN_D8:
+ return runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap);
+ case R8_COMPAT:
+ return runR8CompatRaw(programClasses, proguardConfig);
+ case R8:
+ return runR8Raw(programClasses, proguardConfig);
+ }
+ throw new IllegalArgumentException("Unknown shrinker: " + mode);
+ }
+
protected DexInspector runShrinker(
Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
@@ -54,21 +89,38 @@
throw new IllegalArgumentException("Unknown shrinker: " + mode);
}
- protected DexInspector runR8(List<Class> programClasses, String proguardConfig) throws Exception {
- AndroidApp app = readClasses(programClasses);
- R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
- builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
- return new DexInspector(ToolHelper.runR8(builder.build()));
+ protected AndroidApp runR8Raw(List<Class> programClasses, String proguardConfig)
+ throws Exception {
+ return runR8Raw(programClasses, proguardConfig, null);
}
- protected DexInspector runR8Compat(
+ protected AndroidApp runR8Raw(
+ List<Class> programClasses, String proguardConfig, Consumer<InternalOptions> configure)
+ throws Exception {
+ AndroidApp app = readClassesAndAndriodJar(programClasses);
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
+ builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ return ToolHelper.runR8(builder.build(), configure);
+ }
+
+ protected DexInspector runR8(List<Class> programClasses, String proguardConfig) throws Exception {
+ return new DexInspector(runR8Raw(programClasses, proguardConfig));
+ }
+
+ protected AndroidApp runR8CompatRaw(
List<Class> programClasses, String proguardConfig) throws Exception {
CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
programClasses.forEach(
clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- return new DexInspector(ToolHelper.runR8(builder.build()));
+ return ToolHelper.runR8(builder.build());
+ }
+
+ protected DexInspector runR8Compat(
+ List<Class> programClasses, String proguardConfig) throws Exception {
+ return new DexInspector(runR8CompatRaw(programClasses, proguardConfig));
}
protected DexInspector runR8CompatKeepingMain(Class mainClass, List<Class> programClasses)
@@ -76,41 +128,79 @@
return runR8Compat(programClasses, keepMainProguardConfiguration(mainClass));
}
- protected DexInspector runProguard5(
- List<Class> programClasses, String proguardConfig) throws Exception {
+ protected AndroidApp runProguard5Raw(
+ List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
- Path proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
- ToolHelper.runProguard(
- jarTestClasses(programClasses), proguardedJar, proguardConfigFile, proguardMap);
- return new DexInspector(readJar(proguardedJar), proguardMap);
+ ProcessResult result = ToolHelper.runProguardRaw(
+ jarTestClasses(programClasses),
+ proguardedJar,
+ ToolHelper.getAndroidJar(AndroidApiLevel.N),
+ proguardConfigFile,
+ proguardMap);
+ if (result.exitCode != 0) {
+ fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ return readJar(proguardedJar);
+ }
+
+ protected DexInspector runProguard5(
+ List<Class> programClasses, String proguardConfig) throws Exception {
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ return new DexInspector(
+ runProguard5Raw(programClasses, proguardConfig, proguardMap), proguardMap);
+ }
+
+ protected AndroidApp runProguard6Raw(
+ List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+ Path proguardedJar =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+ ProcessResult result = ToolHelper.runProguard6Raw(
+ jarTestClasses(programClasses),
+ proguardedJar,
+ ToolHelper.getAndroidJar(AndroidApiLevel.N),
+ proguardConfigFile,
+ proguardMap);
+ if (result.exitCode != 0) {
+ fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ return readJar(proguardedJar);
}
protected DexInspector runProguard6(
List<Class> programClasses, String proguardConfig) throws Exception {
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ return new DexInspector(
+ runProguard6Raw(programClasses, proguardConfig, proguardMap), proguardMap);
+ }
+
+ protected AndroidApp runProguard6AndD8Raw(
+ List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
- Path proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
- ToolHelper.runProguard6(
- jarTestClasses(programClasses), proguardedJar, proguardConfigFile, proguardMap);
- return new DexInspector(readJar(proguardedJar), proguardMap);
+ ProcessResult result = ToolHelper.runProguard6Raw(
+ jarTestClasses(programClasses),
+ proguardedJar,
+ ToolHelper.getAndroidJar(AndroidApiLevel.N),
+ proguardConfigFile,
+ proguardMap);
+ if (result.exitCode != 0) {
+ fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ return ToolHelper.runD8(readJar(proguardedJar));
}
protected DexInspector runProguard6AndD8(
List<Class> programClasses, String proguardConfig) throws Exception {
- Path proguardedJar =
- File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
- Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
- Path proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
- FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
- ToolHelper.runProguard6(
- jarTestClasses(programClasses), proguardedJar, proguardConfigFile, proguardMap);
- AndroidApp app = ToolHelper.runD8(readJar(proguardedJar));
- return new DexInspector(app, proguardMap);
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ return new DexInspector(
+ runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap), proguardMap);
}
protected DexInspector runProguardKeepingMain(Class mainClass, List<Class> programClasses)
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
new file mode 100644
index 0000000..65d1763
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.ifrule;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfOnAccessModifierTest extends ProguardCompatabilityTestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(
+ ClassForIf.class, ClassForSubsequent.class,
+ MainForAccessModifierTest.class);
+
+ private final Shrinker shrinker;
+ private final MethodSignature nonPublicMethod;
+ private final MethodSignature publicMethod;
+
+ public IfOnAccessModifierTest(Shrinker shrinker) {
+ this.shrinker = shrinker;
+ nonPublicMethod = new MethodSignature("nonPublicMethod", "void", ImmutableList.of());
+ publicMethod = new MethodSignature("publicMethod", "void", ImmutableList.of());
+ }
+
+ @Parameters(name = "shrinker: {0}")
+ public static Collection<Object> data() {
+ return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8);
+ }
+
+ @Test
+ public void ifOnNonPublic_keepOnPublic() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " !public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}"
+ );
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.method(nonPublicMethod);
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+ classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ if (isR8(shrinker)) {
+ // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
+ // at the 2nd tree shaking.
+ assertThat(classSubject, not(isPresent()));
+ return;
+ }
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.method(nonPublicMethod);
+ assertThat(methodSubject, not(isPresent()));
+ }
+
+ @Test
+ public void ifOnNonPublic_keepOnNonPublic() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " !public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " !public <methods>;",
+ "}"
+ );
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.method(nonPublicMethod);
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+ classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ if (isR8(shrinker)) {
+ // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
+ // at the 2nd tree shaking.
+ assertThat(classSubject, not(isPresent()));
+ return;
+ }
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.method(nonPublicMethod);
+ assertThat(methodSubject, isPresent());
+ assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+ }
+
+ @Test
+ public void ifOnPublic_keepOnPublic() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}"
+ );
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.method(nonPublicMethod);
+ assertThat(methodSubject, not(isPresent()));
+
+ classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.method(nonPublicMethod);
+ assertThat(methodSubject, not(isPresent()));
+ }
+
+ @Test
+ public void ifOnPublic_keepOnNonPublic() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " !public <methods>;",
+ "}"
+ );
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.method(nonPublicMethod);
+ assertThat(methodSubject, not(isPresent()));
+
+ classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.method(publicMethod);
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.method(nonPublicMethod);
+ if (isR8(shrinker)) {
+ // TODO(b/72109068): if kept in the 1st tree shaking, should be kept after publicizing.
+ assertThat(methodSubject, not(isPresent()));
+ return;
+ }
+ assertThat(methodSubject, isPresent());
+ assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
new file mode 100644
index 0000000..1b5bd75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.ifrule;
+
+class ClassForIf {
+ ClassForIf() {
+ }
+
+ synchronized void nonPublicMethod() {
+ System.out.println("ClassForIf::nonPublicMethod");
+ }
+
+ synchronized public void publicMethod() {
+ System.out.println("ClassForIf::publicMethod");
+ }
+}
+
+class ClassForSubsequent {
+ ClassForSubsequent() {
+ }
+
+ synchronized void nonPublicMethod() {
+ System.out.println("ClassForSubsequent::nonPublicMethod");
+ }
+
+ synchronized public void publicMethod() {
+ System.out.println("ClassForSubsequent::publicMethod");
+ }
+}
+
+class MainForAccessModifierTest {
+ public static void callIfNonPublic() {
+ new ClassForIf().nonPublicMethod();
+ }
+
+ public static void callIfPublic() {
+ new ClassForIf().publicMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
new file mode 100644
index 0000000..14391ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.inlining;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class A {
+ static public int a() {
+ try {
+ String p = A.class.getPackage().getName();
+ Class.forName(p + ".D");
+ } catch (ClassNotFoundException e) {
+ return 2;
+ }
+ return 1;
+ }
+}
+
+class B {
+ // Depending on inlining option, this method is kept to make inlining of A.a() infeasible.
+ void x() {
+ System.out.print("" + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a());
+ }
+}
+
+class D {
+}
+
+class Main {
+ public static void main(String[] args) {
+ System.out.print("" + A.a());
+ }
+}
+
+@RunWith(Parameterized.class)
+public class IfRuleWithInlining extends ProguardCompatabilityTestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(
+ A.class, B.class, D.class, Main.class);
+
+ private final Shrinker shrinker;
+ private final boolean inlineMethod;
+
+ public IfRuleWithInlining(Shrinker shrinker, boolean inlineMethod) {
+ this.shrinker = shrinker;
+ this.inlineMethod = inlineMethod;
+ }
+
+ @Parameters(name = "shrinker: {0} inlineMethod: {1}")
+ public static Collection<Object[]> data() {
+ // We don't run this on Proguard, as triggering inlining in Proguard is out of our control.
+ return ImmutableList.of(
+ new Object[]{Shrinker.R8, true},
+ new Object[]{Shrinker.R8, false}
+ );
+ }
+
+ private void check(AndroidApp app) throws Exception {
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazzA = inspector.clazz(A.class);
+ assertThat(clazzA, isPresent());
+ // A.a might be inlined.
+ assertEquals(!inlineMethod, clazzA.method("int", "a", ImmutableList.of()).isPresent());
+ // TODO(110148109): class D should be present - inlining or not.
+ assertEquals(!inlineMethod, inspector.clazz(D.class).isPresent());
+ ProcessResult result = runOnArtRaw(app, Main.class.getName());
+ assertEquals(0, result.exitCode);
+ // TODO(110148109): Output should be the same - inlining or not.
+ assertEquals(!inlineMethod ? "1" : "2", result.stdout);
+ }
+
+ @Test
+ public void testMergedClassMethodInIfRule() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **.Main { public static void main(java.lang.String[]); }",
+ inlineMethod
+ ? "-alwaysinline class **.A { int a(); }"
+ : "-keep class **.B { *; }",
+ inlineMethod
+ ? "-checkdiscard class **.A { int a(); }"
+ : "",
+ "-if class **.A { static int a(); }",
+ "-keep class **.D",
+ "-dontobfuscate"
+ );
+
+ check(runShrinkerRaw(shrinker, CLASSES, config));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
new file mode 100644
index 0000000..b484d73
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -0,0 +1,156 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class A {
+ int x = 1;
+ int a() throws ClassNotFoundException {
+ // Class D is expected to be kept - vertical class merging or not. The -if rule say that if
+ // the method A.a is in the output, then class D is needed.
+ String p =getClass().getPackage().getName();
+ Class.forName(p + ".D");
+ return 4;
+ }
+}
+
+class B extends A {
+ int y = 2;
+ int b() {
+ return 5;
+ }
+}
+
+class C extends B {
+ int z = 3;
+ int c() {
+ return 6;
+ }
+}
+
+class D {
+}
+
+class Main {
+ public static void main(String[] args) throws ClassNotFoundException {
+ C c = new C();
+ System.out.print("" + c.x + "" + c.y + "" + c.z + "" + c.a() + "" + c.b() + "" + c.c());
+ }
+}
+
+@RunWith(Parameterized.class)
+public class IfRuleWithVerticalClassMerging extends ProguardCompatabilityTestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(
+ A.class, B.class, C.class, D.class, Main.class);
+
+ private final Shrinker shrinker;
+ private final boolean enableClassMerging;
+
+ public IfRuleWithVerticalClassMerging(Shrinker shrinker, boolean enableClassMerging) {
+ this.shrinker = shrinker;
+ this.enableClassMerging = enableClassMerging;
+ }
+
+ @Parameters(name = "shrinker: {0} classMerging: {1}")
+ public static Collection<Object[]> data() {
+ // We don't run this on Proguard, as Proguard does not merge A into B.
+ return ImmutableList.of(
+ new Object[]{Shrinker.R8, true},
+ new Object[]{Shrinker.R8, false}
+ );
+ }
+
+ private void configure(InternalOptions options) {
+ options.enableClassMerging = enableClassMerging;
+ }
+
+ @Override
+ protected AndroidApp runR8Raw(
+ List<Class> programClasses, String proguardConfig) throws Exception {
+ return super.runR8Raw(programClasses, proguardConfig, this::configure);
+ }
+
+ private void check(AndroidApp app) throws Exception {
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazzA = inspector.clazz(A.class);
+ assertEquals(!enableClassMerging, clazzA.isPresent());
+ ClassSubject clazzB = inspector.clazz(B.class);
+ assertThat(clazzB, isPresent());
+ ClassSubject clazzD = inspector.clazz(D.class);
+ // TODO(110141157): Class D should be kept - vertical class merging or not.
+ assertEquals(!enableClassMerging, clazzD.isPresent());
+
+ ProcessResult result = runOnArtRaw(app, Main.class.getName());
+ // TODO(110141157): The code should run - vertical class merging or not.
+ assertEquals(enableClassMerging ? 1 : 0, result.exitCode);
+ if (!enableClassMerging) {
+ assertEquals("123456", result.stdout);
+ }
+ }
+
+ @Test
+ public void testMergedClassInIfRule() throws Exception {
+ // Class C is kept, meaning that it will not be touched.
+ // Class A will be merged into class B.
+ List<String> config = ImmutableList.of(
+ "-keep class **.Main { public static void main(java.lang.String[]); }",
+ "-keep class **.C",
+ "-if class **.A",
+ "-keep class **.D",
+ "-dontobfuscate"
+ );
+
+ check(runShrinkerRaw(shrinker, CLASSES, config));
+ }
+
+ @Test
+ public void testMergedClassFieldInIfRule() throws Exception {
+ // Class C is kept, meaning that it will not be touched.
+ // Class A will be merged into class B.
+ // Main.main access A.x, so that field exists satisfying the if rule.
+ List<String> config = ImmutableList.of(
+ "-keep class **.Main { public static void main(java.lang.String[]); }",
+ "-keep class **.C",
+ "-if class **.A { int x; }",
+ "-keep class **.D",
+ "-dontobfuscate"
+ );
+
+ check(runShrinkerRaw(shrinker, CLASSES, config));
+ }
+
+ @Test
+ public void testMergedClassMethodInIfRule() throws Exception {
+ // Class C is kept, meaning that it will not be touched.
+ // Class A will be merged into class B.
+ // Main.main access A.a(), that method exists satisfying the if rule.
+ List<String> config = ImmutableList.of(
+ "-keep class **.Main { public static void main(java.lang.String[]); }",
+ "-keep class **.C",
+ "-if class **.A { int a(); }",
+ "-keep class **.D",
+ "-dontobfuscate"
+ );
+
+ check(runShrinkerRaw(shrinker, CLASSES, config));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index e7e882a..fd32ec7 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -886,7 +886,31 @@
@Override
public MethodSignature getOriginalSignature() {
MethodSignature signature = getFinalSignature();
- MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+ if (clazz.naming == null) {
+ return signature;
+ }
+
+ // Map the parameters and return type to original names. This is needed as the in the
+ // Proguard map the names on the left side are the original names. E.g.
+ //
+ // X -> a
+ // X method(X) -> a
+ //
+ // whereas the final signature is for X.a is "a (a)"
+ String[] OriginalParameters = new String[signature.parameters.length];
+ for (int i = 0; i < OriginalParameters.length; i++) {
+ String obfuscated = signature.parameters[i];
+ String original = originalToObfuscatedMapping.inverse().get(obfuscated);
+ OriginalParameters[i] = original != null ? original : obfuscated;
+ }
+ String obfuscatedReturnType = signature.type;
+ String originalReturnType = originalToObfuscatedMapping.inverse().get(obfuscatedReturnType);
+ String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
+
+ MethodSignature lookupSignature =
+ new MethodSignature(signature.name, returnType, OriginalParameters);
+
+ MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
return memberNaming != null
? (MethodSignature) memberNaming.getOriginalSignature()
: signature;
@@ -1035,7 +1059,24 @@
@Override
public FieldSignature getOriginalSignature() {
FieldSignature signature = getFinalSignature();
- MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+ if (clazz.naming == null) {
+ return signature;
+ }
+
+ // Map the type to the original name. This is needed as the in the Proguard map the
+ // names on the left side are the original names. E.g.
+ //
+ // X -> a
+ // X field -> a
+ //
+ // whereas the final signature is for X.a is "a a"
+ String obfuscatedType = signature.type;
+ String originalType = originalToObfuscatedMapping.inverse().get(obfuscatedType);
+ String fieldType = originalType != null ? originalType : obfuscatedType;
+
+ FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
+
+ MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
return memberNaming != null
? (FieldSignature) memberNaming.getOriginalSignature()
: signature;