blob: bcb02da59a4b39f6da1eb20b948737a29f935704 [file] [log] [blame]
// 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.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
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.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.origin.Origin;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
@RunWith(Parameterized.class)
public class MinifierClassSignatureTest extends TestBase {
/*
class Simple {
}
class Base<T> {
}
class Outer<T> {
class Inner {
class InnerInner {
}
class ExtendsInnerInner extends InnerInner {
}
}
class ExtendsInner extends Inner {
}
}
*/
String baseSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
String outerSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
String extendsInnerSignature = "LOuter<TT;>.Inner;";
String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
private Backend backend;
@Parameters(name = "Backend: {0}")
public static Backend[] data() {
return ToolHelper.getBackends();
}
public MinifierClassSignatureTest(Backend backend) {
this.backend = backend;
}
private byte[] dumpSimple(String classSignature) throws Exception {
ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
String signature = classSignature;
cw.visit(V1_8, ACC_SUPER, "Simple", signature, "java/lang/Object", null);
{
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();
}
private byte[] dumpBase(String classSignature) throws Exception {
final String javacClassSignature = baseSignature;
ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
String signature = classSignature != null ? classSignature : javacClassSignature;
cw.visit(V1_8, ACC_SUPER, "Base", signature, "java/lang/Object", null);
{
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();
}
private byte[] dumpOuter(String classSignature) {
final String javacClassSignature = outerSignature;
ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
String signature = classSignature != null ? classSignature : javacClassSignature;
cw.visit(V1_8, ACC_SUPER, "Outer", signature, "java/lang/Object", null);
cw.visitInnerClass("Outer$ExtendsInner", "Outer", "ExtendsInner", 0);
cw.visitInnerClass("Outer$Inner", "Outer", "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();
}
cw.visitEnd();
return cw.toByteArray();
}
private byte[] dumpInner(String classSignature) {
final String javacClassSignature = null;
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
String signature = classSignature != null ? classSignature : javacClassSignature;
cw.visit(V1_8, ACC_SUPER, "Outer$Inner", signature, "java/lang/Object", null);
cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
cw.visitInnerClass("Outer$Inner$ExtendsInnerInner", "Outer$Inner", "ExtendsInnerInner", 0);
cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
{
fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LOuter;", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(0, "<init>", "(LOuter;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "Outer$Inner", "this$0", "LOuter;");
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 byte[] dumpExtendsInner(String classSignature) throws Exception {
final String javacClassSignature = extendsInnerSignature;
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
String signature = classSignature != null ? classSignature : javacClassSignature;
cw.visit(V1_8, ACC_SUPER, "Outer$ExtendsInner", signature, "Outer$Inner", null);
cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
cw.visitInnerClass("Outer$ExtendsInner", "Outer", "ExtendsInner", 0);
{
fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LOuter;", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(0, "<init>", "(LOuter;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "Outer$ExtendsInner", "this$0", "LOuter;");
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, "Outer$Inner", "<init>", "(LOuter;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
private byte[] dumpInnerInner(String classSignature) throws Exception {
final String javacClassSignature = null;
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
String signature = classSignature != null ? classSignature : javacClassSignature;
cw.visit(V1_8, ACC_SUPER, "Outer$Inner$InnerInner", signature, "java/lang/Object", null);
cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
{
fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$1", "LOuter$Inner;", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(0, "<init>", "(LOuter$Inner;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "Outer$Inner$InnerInner", "this$1", "LOuter$Inner;");
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 byte[] dumpExtendsInnerInner(String classSignature) throws Exception {
final String javacClassSignature = extendsInnerInnerSignature;
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
String signature = classSignature != null ? classSignature : javacClassSignature;
cw.visit(V1_8, ACC_SUPER, "Outer$Inner$ExtendsInnerInner", signature, "Outer$Inner$InnerInner",
null);
cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
cw.visitInnerClass("Outer$Inner$ExtendsInnerInner", "Outer$Inner", "ExtendsInnerInner", 0);
{
fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$1", "LOuter$Inner;", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(0, "<init>", "(LOuter$Inner;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "Outer$Inner$ExtendsInnerInner", "this$1", "LOuter$Inner;");
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, "Outer$Inner$InnerInner", "<init>", "(LOuter$Inner;)V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
public void runTest(
ImmutableMap<String, String> signatures,
Consumer<DiagnosticsChecker> diagnostics,
Consumer<CodeInspector> inspect)
throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
CodeInspector inspector =
new CodeInspector(
ToolHelper.runR8(
R8Command.builder(checker)
.addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
.addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
.addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
.addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
.addClassProgramData(
dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
.addClassProgramData(
dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
.addClassProgramData(
dumpExtendsInnerInner(signatures.get("Outer$Inner$ExtendsInnerInner")),
Origin.unknown())
.addProguardConfiguration(
ImmutableList.of(
"-keepattributes InnerClasses,EnclosingMethod,Signature",
"-keep,allowobfuscation class **"),
Origin.unknown())
.setProgramConsumer(emptyConsumer(backend))
.addLibraryFiles(runtimeJar(backend))
.setProguardMapConsumer(StringConsumer.emptyConsumer())
.build()));
// All classes are kept, and renamed.
assertThat(inspector.clazz("Simple"), isRenamed());
assertThat(inspector.clazz("Base"), isRenamed());
assertThat(inspector.clazz("Outer"), isRenamed());
assertThat(inspector.clazz("Outer$Inner"), isRenamed());
assertThat(inspector.clazz("Outer$ExtendsInner"), isRenamed());
assertThat(inspector.clazz("Outer$Inner$InnerInner"), isRenamed());
assertThat(inspector.clazz("Outer$Inner$ExtendsInnerInner"), isRenamed());
// Test that classes with have their original signature if the default was provided.
if (!signatures.containsKey("Simple")) {
assertNull(inspector.clazz("Simple").getOriginalSignatureAttribute());
}
if (!signatures.containsKey("Base")) {
assertEquals(baseSignature, inspector.clazz("Base").getOriginalSignatureAttribute());
}
if (!signatures.containsKey("Outer")) {
assertEquals(outerSignature, inspector.clazz("Outer").getOriginalSignatureAttribute());
}
if (!signatures.containsKey("Outer$Inner")) {
assertNull(inspector.clazz("Outer$Inner").getOriginalSignatureAttribute());
}
if (!signatures.containsKey("Outer$ExtendsInner")) {
assertEquals(extendsInnerSignature,
inspector.clazz("Outer$ExtendsInner").getOriginalSignatureAttribute());
}
if (!signatures.containsKey("Outer$Inner$InnerInner")) {
assertNull(inspector.clazz("Outer$Inner$InnerInner").getOriginalSignatureAttribute());
}
if (!signatures.containsKey("Outer$Inner$ExtendsInnerInner")) {
assertEquals(extendsInnerInnerSignature,
inspector.clazz("Outer$Inner$ExtendsInnerInner").getOriginalSignatureAttribute());
}
diagnostics.accept(checker);
inspect.accept(inspector);
}
private void testSingleClass(String name, String signature,
Consumer<DiagnosticsChecker> diagnostics,
Consumer<CodeInspector> 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(CodeInspector inspector) {
}
private void noSignatureAttribute(ClassSubject clazz) {
assertNull(clazz.getFinalSignatureAttribute());
assertNull(clazz.getOriginalSignatureAttribute());
}
@Test
public void originalJavacSignatures() throws Exception {
// Test using the signatures generated by javac.
runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
}
@Test
public void classSignature_empty() throws Exception {
testSingleClass("Outer", "", this::noWarnings, inspector -> {
ClassSubject outer = inspector.clazz("Outer");
assertNull(outer.getFinalSignatureAttribute());
assertNull(outer.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureOuter_valid() throws Exception {
// class Outer<T extends Simple> extends Base<T>
String signature = "<T:LSimple;>LBase<TT;>;";
testSingleClass("Outer", signature, this::noWarnings, inspector -> {
ClassSubject outer = inspector.clazz("Outer");
ClassSubject simple = inspector.clazz("Simple");
ClassSubject base = inspector.clazz("Base");
String baseDescriptorWithoutSemicolon =
base.getFinalDescriptor().substring(0, base.getFinalDescriptor().length() - 1);
String minifiedSignature =
"<T:" + simple.getFinalDescriptor() + ">" + baseDescriptorWithoutSemicolon + "<TT;>;";
assertEquals(minifiedSignature, outer.getFinalSignatureAttribute());
assertEquals(signature, outer.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureExtendsInner_valid() throws Exception {
String signature = "LOuter<TT;>.Inner;";
testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
ClassSubject extendsInner = inspector.clazz("Outer$ExtendsInner");
ClassSubject outer = inspector.clazz("Outer");
ClassSubject inner = inspector.clazz("Outer$Inner");
String outerDescriptorWithoutSemicolon =
outer.getFinalDescriptor().substring(0, outer.getFinalDescriptor().length() - 1);
String innerFinalDescriptor = inner.getFinalDescriptor();
String innerLastPart =
innerFinalDescriptor.substring(innerFinalDescriptor.indexOf("$") + 1);
String minifiedSignature = outerDescriptorWithoutSemicolon + "<TT;>." + innerLastPart;
assertEquals(minifiedSignature, extendsInner.getFinalSignatureAttribute());
assertEquals(signature, extendsInner.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureOuter_classNotFound() throws Exception {
String signature = "<T:LNotFound;>LAlsoNotFound;";
testSingleClass("Outer", signature, this::noWarnings, inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer");
assertEquals(signature, outer.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureExtendsInner_innerClassNotFound() throws Exception {
String signature = "LOuter<TT;>.NotFound;";
testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
assertEquals(signature, outer.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureExtendsInner_outerAndInnerClassNotFound() throws Exception {
String signature = "LNotFound<TT;>.AlsoNotFound;";
testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
assertEquals(signature, outer.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureExtendsInner_nestedInnerClassNotFound() throws Exception {
String signature = "LOuter<TT;>.Inner.NotFound;";
testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
assertEquals(signature, outer.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureExtendsInner_multipleMestedInnerClassesNotFound() throws Exception {
String signature = "LOuter<TT;>.NotFound.AlsoNotFound;";
testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
assertEquals(signature, outer.getOriginalSignatureAttribute());
});
}
@Test
public void classSignatureOuter_invalid() throws Exception {
testSingleClass("Outer", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
"Invalid signature 'X' for class Outer", "Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
}
@Test
public void classSignatureOuter_invalidEnd() throws Exception {
testSingleClass("Outer", "<L", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
"Invalid signature '<L' for class Outer", "Unexpected end of signature at position 3");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
}
@Test
public void classSignatureExtendsInner_invalid() throws Exception {
testSingleClass("Outer$ExtendsInner", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
"Invalid signature 'X' for class Outer$ExtendsInner", "Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
}
@Test
public void classSignatureExtendsInnerInner_invalid() throws Exception {
testSingleClass("Outer$Inner$ExtendsInnerInner", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
"Invalid signature 'X' for class Outer$Inner$ExtendsInnerInner",
"Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
}
@Test
public void multipleWarnings() throws Exception {
runTest(ImmutableMap.of(
"Outer", "X",
"Outer$ExtendsInner", "X",
"Outer$Inner$ExtendsInnerInner", "X"), diagnostics -> {
assertEquals(3, diagnostics.warnings.size());
}, inspector -> {
noSignatureAttribute(inspector.clazz("Outer"));
noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner"));
});
}
@Test
public void regress80029761() throws Exception {
String signature = "LOuter<TT;>.com/example/Inner;";
testSingleClass("Outer$ExtendsInner", signature, diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(
diagnostics.warnings.get(0),
this::isOriginUnknown,
"Invalid signature '" + signature + "' for class Outer$ExtendsInner",
"Expected ; at position 16");
}, inspector -> {
noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
});
}
}