blob: adfbcecddf0221f17c616625d622920dce09355f [file] [log] [blame]
// Copyright (c) 2019, 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.arraytypes;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.function.Consumer;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
public class ArrayTypesTest extends TestBase {
private final TestParameters parameters;
private static String packageName;
private static String arrayBaseTypeDescriptor;
private static String arrayTypeDescriptor;
private static String generatedTestClassName;
private static String expectedOutput;
public ArrayTypesTest(TestParameters parameters) {
this.parameters = parameters;
}
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimes().build();
}
@BeforeClass
public static void setup() {
packageName = ArrayTypesTest.class.getPackage().getName();
arrayBaseTypeDescriptor =
DescriptorUtils.getDescriptorFromClassBinaryName(
DescriptorUtils.getBinaryNameFromJavaType(
A.class.getTypeName()));
arrayTypeDescriptor = "[" + arrayBaseTypeDescriptor;
generatedTestClassName = packageName + "." + "GeneratedTestClass";
expectedOutput = StringUtils.lines(
"javac code:",
"Length: EQ",
"Hashcode: EQ",
"toString: EQ",
"ASM code:",
"Clones: NE",
"Hashcode: EQ",
"Compare with Object: true",
"Compare with array type: true",
"toString: true",
"Got expected exception",
"Done");
}
private void runR8Test(boolean enableMinification) throws Exception {
testForR8(parameters.getBackend())
.minification(enableMinification)
.addProgramClasses(Main.class, A.class)
.addProgramClassFileData(generateTestClass())
.addKeepMainRule(Main.class)
.addKeepRules("-keep class " + generatedTestClassName + " { test(...); }")
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(expectedOutput);
}
@Test
public void testR8() throws Exception {
runR8Test(true);
}
@Test
public void testR8NoMinification() throws Exception {
runR8Test(false);
}
@Test
public void testR8ApplyMapping() throws Exception {
// Rename the array type (keep it in the same package for access reasons).
Path mappingFile = temp.newFile("mapping.txt").toPath();
FileUtils.writeTextFile(
mappingFile,
StringUtils.lines(
A.class.getTypeName() + " -> " + packageName + ".a:"));
testForR8(parameters.getBackend())
.addProgramClasses(Main.class, A.class)
.addProgramClassFileData(generateTestClass())
.addKeepMainRule(Main.class)
.addKeepRules("-applymapping " + mappingFile.toAbsolutePath())
.noMinification()
.noTreeShaking()
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(expectedOutput);
}
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForD8()
.addProgramClasses(Main.class, A.class)
.addProgramClassFileData(generateTestClass())
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), Main.class)
.writeProcessResult(System.out)
.assertSuccessWithOutput(expectedOutput);
}
public static byte[] generateTestClass() {
ClassWriter classWriter = new ClassWriter(0);
MethodVisitor mv;
classWriter.visit(
Opcodes.V1_8,
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_ENUM,
DescriptorUtils.getBinaryNameFromJavaType(generatedTestClassName),
null,
"java/lang/Object",
null);
{
mv = classWriter.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
"test", "(" + arrayTypeDescriptor + ")V",
null,
null);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("ASM code:");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// Invoke clone using both java.lang.Object and array type an holder.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/lang/Object", "clone", "()Ljava/lang/Object;", false);
mv.visitVarInsn(Opcodes.ASTORE, 1);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "clone", "()Ljava/lang/Object;", false);
mv.visitVarInsn(Opcodes.ASTORE, 2);
printCompareObjectsIdentical("Clones: ", mv, (mvCopy) -> {
assert mv == mvCopy;
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
});
printCompareIntergers("Hashcode: ", mv, (mvCopy) -> {
assert mv == mvCopy;
// Invoke hashCode using both java.lang.Object and array type an holder.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "hashCode", "()I", false);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "hashCode", "()I", false);
});
printBoolean("Compare with Object: ", mv, (mvCopy) -> {
assert mv == mvCopy;
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
});
printBoolean("Compare with array type: ", mv, (mvCopy) -> {
assert mv == mvCopy;
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "equals", "(Ljava/lang/Object;)Z", false);
});
printBoolean("toString: ", mv, (mvCopy) -> {
assert mv == mvCopy;
// Invoke toString using both java.lang.Object and array type an holder.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "toString", "()Ljava/lang/String;", false);
// Compare the toString strings.
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
});
// Finally invoke a method not present on an array type.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "notPresent", "()Ljava/lang/String;", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
public static void printCompareIntergers(
String header, MethodVisitor mv, Consumer<MethodVisitor> consumer) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(header);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
consumer.accept(mv);
Label neLabel = new Label();
Label printLabel = new Label();
mv.visitJumpInsn(Opcodes.IF_ICMPNE, neLabel);
mv.visitLdcInsn("EQ");
mv.visitJumpInsn(Opcodes.GOTO, printLabel);
mv.visitLabel(neLabel);
mv.visitLdcInsn("NE");
mv.visitLabel(printLabel);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
public static void printCompareObjectsIdentical(
String header, MethodVisitor mv, Consumer<MethodVisitor> consumer) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(header);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
consumer.accept(mv);
Label neLabel = new Label();
Label printLabel = new Label();
mv.visitJumpInsn(Opcodes.IF_ACMPNE, neLabel);
mv.visitLdcInsn("EQ");
mv.visitJumpInsn(Opcodes.GOTO, printLabel);
mv.visitLabel(neLabel);
mv.visitLdcInsn("NE");
mv.visitLabel(printLabel);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
public static void printBoolean(
String header, MethodVisitor mv, Consumer<MethodVisitor> consumer) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(header);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
consumer.accept(mv);
Label neLabel = new Label();
Label printLabel = new Label();
mv.visitJumpInsn(Opcodes.IFNE, neLabel);
mv.visitLdcInsn("false");
mv.visitJumpInsn(Opcodes.GOTO, printLabel);
mv.visitLabel(neLabel);
mv.visitLdcInsn("true");
mv.visitLabel(printLabel);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
}
class A {
}
class Main {
public static void main(String[] args) throws Exception {
A[] array = new A[2];
A[] clone = array.clone();
System.out.println("javac code:");
if (array.length == clone.length) {
System.out.println("Length: EQ");
}
if (array.hashCode() == array.hashCode()) {
System.out.println("Hashcode: EQ");
}
if (array.toString().equals(array.toString())) {
System.out.println("toString: EQ");
}
Class<?> generatedTestClass =
Class.forName(Main.class.getPackage().getName() + "." + "GeneratedTestClass");
Method testMethod = generatedTestClass.getDeclaredMethod("test", array.getClass());
try {
testMethod.invoke(null, new Object[]{array});
} catch (java.lang.reflect.InvocationTargetException e) {
System.out.println("Got expected exception");
}
System.out.println("Done");
}
}