| // 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.overloadaggressively; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.jasmin.JasminBuilder; |
| import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder; |
| import com.android.tools.r8.jasmin.JasminTestBase; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.FieldSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class ValidNameConflictTest extends JasminTestBase { |
| private static final String REPEATED_NAME = "hopeTheresNoSuchNameInRuntimeLibraries"; |
| |
| private final Backend backend; |
| |
| @Parameterized.Parameters(name = "Backend: {0}") |
| public static Backend[] data() { |
| return ToolHelper.getBackends(); |
| } |
| |
| public ValidNameConflictTest(Backend backend) { |
| this.backend = backend; |
| } |
| |
| private final String CLASS_NAME = "Example"; |
| private final String SUPER_CLASS = "Super"; |
| private final String ANOTHER_CLASS = "Test"; |
| private final String MSG = "Expected to be seen at the end."; |
| |
| private Iterable<String> buildCodeForVisitingDeclaredMembers( |
| Iterable<String> prologue, Iterable<String> argumentLoadingAndCall) { |
| return Iterables.concat( |
| prologue, |
| ImmutableList.of( |
| " astore_0", // Member[] |
| " aload_0", |
| " arraylength", |
| " istore_1", // Member[].length |
| " iconst_0", |
| " istore_2", // counter |
| "loop:", |
| " iload_2", |
| " iload_1", |
| " if_icmpge end", |
| " aload_0", |
| " iload_2", |
| " aaload", // Member[counter] |
| " astore_3", |
| " getstatic java/lang/System/out Ljava/io/PrintStream;", |
| " aload_3"), |
| argumentLoadingAndCall, |
| ImmutableList.of( |
| " invokevirtual java/io/PrintStream/println(Ljava/lang/Object;)V", |
| " iinc 2 1", // counter++ |
| " goto loop", |
| "end:", |
| " return")); |
| } |
| |
| private JasminBuilder buildFieldNameConflictClassFile() { |
| JasminBuilder builder = new JasminBuilder(); |
| ClassBuilder classBuilder = builder.addClass(CLASS_NAME); |
| classBuilder.addStaticField(REPEATED_NAME, "Ljava/lang/Object;", null); |
| classBuilder.addStaticField(REPEATED_NAME, "Ljava/lang/String;", "\"" + MSG + "\""); |
| classBuilder.addMainMethod( |
| buildCodeForVisitingDeclaredMembers( |
| ImmutableList.of( |
| ".limit stack 3", |
| ".limit locals 4", |
| " ldc " + CLASS_NAME, |
| " invokevirtual java/lang/Class/getDeclaredFields()[Ljava/lang/reflect/Field;"), |
| ImmutableList.of( |
| " aconst_null", |
| " invokevirtual" |
| + " java/lang/reflect/Field/get(Ljava/lang/Object;)Ljava/lang/Object;"))); |
| return builder; |
| } |
| |
| private ProcessResult runRaw(AndroidApp app, String main) throws IOException { |
| if (backend == Backend.DEX) { |
| return runOnArtRaw(app, main); |
| } else { |
| assert backend == Backend.CF; |
| return runOnJavaRawNoVerify(app, main, Collections.emptyList()); |
| } |
| } |
| |
| @Test |
| public void remainFieldNameConflict_keepRules() throws Exception { |
| JasminBuilder builder = buildFieldNameConflictClassFile(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep public class " + CLASS_NAME + " {\n" |
| + " public static void main(java.lang.String[]);\n" |
| + " static <fields>;" |
| + "}\n" |
| + "-printmapping\n", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject clazz = codeInspector.clazz(CLASS_NAME); |
| assertTrue(clazz.isPresent()); |
| FieldSubject f1 = clazz.field("java.lang.String", REPEATED_NAME); |
| assertTrue(f1.isPresent()); |
| assertFalse(f1.isRenamed()); |
| FieldSubject f2 = clazz.field("java.lang.Object", REPEATED_NAME); |
| assertTrue(f2.isPresent()); |
| assertFalse(f2.isRenamed()); |
| assertEquals(f1.getFinalName(), f2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals(javaOutput.stdout, output.stdout); |
| } |
| |
| @Test |
| public void resolveFieldNameConflict_no_options() throws Exception { |
| JasminBuilder builder = buildFieldNameConflictClassFile(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject clazz = codeInspector.clazz(CLASS_NAME); |
| assertTrue(clazz.isPresent()); |
| FieldSubject f1 = clazz.field("java.lang.String", REPEATED_NAME); |
| assertTrue(f1.isPresent()); |
| assertTrue(f1.isRenamed()); |
| FieldSubject f2 = clazz.field("java.lang.Object", REPEATED_NAME); |
| assertTrue(f2.isPresent()); |
| assertTrue(f2.isRenamed()); |
| assertNotEquals(f1.getFinalName(), f2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals(javaOutput.stdout, output.stdout); |
| } |
| |
| @Test |
| public void remainFieldNameConflict_overloadaggressively() throws Exception { |
| JasminBuilder builder = buildFieldNameConflictClassFile(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-overloadaggressively", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject clazz = codeInspector.clazz(CLASS_NAME); |
| assertTrue(clazz.isPresent()); |
| FieldSubject f1 = clazz.field("java.lang.String", REPEATED_NAME); |
| assertTrue(f1.isPresent()); |
| assertTrue(f1.isRenamed()); |
| FieldSubject f2 = clazz.field("java.lang.Object", REPEATED_NAME); |
| assertTrue(f2.isPresent()); |
| assertTrue(f2.isRenamed()); |
| assertEquals(f1.getFinalName(), f2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals(javaOutput.stdout, output.stdout); |
| } |
| |
| private JasminBuilder buildMethodNameConflictClassFile() { |
| JasminBuilder builder = new JasminBuilder(); |
| ClassBuilder classBuilder = builder.addClass(ANOTHER_CLASS); |
| classBuilder.addStaticMethod( |
| REPEATED_NAME, ImmutableList.of(), "Ljava/lang/Object;", "aconst_null", "areturn"); |
| classBuilder.addStaticMethod( |
| REPEATED_NAME, ImmutableList.of(), "Ljava/lang/String;", "ldc \"" + MSG + "\"", "areturn"); |
| classBuilder = builder.addClass(CLASS_NAME); |
| classBuilder.addMainMethod( |
| buildCodeForVisitingDeclaredMembers( |
| ImmutableList.of( |
| ".limit stack 3", |
| ".limit locals 4", |
| " ldc " + ANOTHER_CLASS, |
| " invokevirtual java/lang/Class/getDeclaredMethods()[Ljava/lang/reflect/Method;"), |
| ImmutableList.of( |
| " aconst_null", |
| " aconst_null", |
| " checkcast [Ljava/lang/Object;", |
| " invokevirtual java/lang/reflect/Method/invoke" |
| + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"))); |
| return builder; |
| } |
| |
| @Test |
| public void remainMethodNameConflict_keepRules() throws Exception { |
| JasminBuilder builder = buildMethodNameConflictClassFile(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep class " + ANOTHER_CLASS + " {\n" |
| + " static <methods>;" |
| + "}\n", |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS); |
| assertTrue(clazz.isPresent()); |
| MethodSubject m1 = clazz.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m1.isPresent()); |
| assertFalse(m1.isRenamed()); |
| MethodSubject m2 = clazz.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m2.isPresent()); |
| assertFalse(m2.isRenamed()); |
| assertEquals(m1.getFinalName(), m2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals(javaOutput.stdout, output.stdout); |
| } |
| |
| @Test |
| public void resolveMethodNameConflict_no_options() throws Exception { |
| JasminBuilder builder = buildMethodNameConflictClassFile(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS); |
| assertTrue(clazz.isPresent()); |
| MethodSubject m1 = clazz.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m1.isPresent()); |
| assertTrue(m1.isRenamed()); |
| MethodSubject m2 = clazz.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m2.isPresent()); |
| assertTrue(m2.isRenamed()); |
| assertNotEquals(m1.getFinalName(), m2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals( |
| new HashSet<>(StringUtils.splitLines(javaOutput.stdout)), |
| new HashSet<>(StringUtils.splitLines(output.stdout))); |
| } |
| |
| @Test |
| public void remainMethodNameConflict_overloadaggressively() throws Exception { |
| JasminBuilder builder = buildMethodNameConflictClassFile(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-overloadaggressively", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS); |
| assertTrue(clazz.isPresent()); |
| MethodSubject m1 = clazz.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m1.isPresent()); |
| assertTrue(m1.isRenamed()); |
| MethodSubject m2 = clazz.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m2.isPresent()); |
| assertTrue(m2.isRenamed()); |
| if (backend == Backend.DEX) { |
| assertNotEquals(m1.getFinalName(), m2.getFinalName()); |
| } else { |
| assertEquals(m1.getFinalName(), m2.getFinalName()); |
| } |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals(javaOutput.stdout, output.stdout); |
| } |
| |
| private JasminBuilder buildMethodNameConflictInHierarchy() { |
| JasminBuilder builder = new JasminBuilder(); |
| ClassBuilder classBuilder = builder.addClass(SUPER_CLASS); |
| classBuilder.addVirtualMethod( |
| REPEATED_NAME, ImmutableList.of(), "Ljava/lang/Object;", "aconst_null", "areturn"); |
| classBuilder.addVirtualMethod( |
| REPEATED_NAME, ImmutableList.of(), "Ljava/lang/String;", "ldc \"" + MSG + "\"", "areturn"); |
| classBuilder = builder.addClass(ANOTHER_CLASS, SUPER_CLASS); |
| classBuilder.addVirtualMethod( |
| REPEATED_NAME, |
| ImmutableList.of(), |
| "Ljava/lang/Object;", |
| "aload_0", |
| "invokespecial " + SUPER_CLASS + "/" + REPEATED_NAME + "()Ljava/lang/Object;", |
| "areturn"); |
| classBuilder.addVirtualMethod( |
| REPEATED_NAME, |
| ImmutableList.of(), |
| "Ljava/lang/String;", |
| "aload_0", |
| "invokespecial " + SUPER_CLASS + "/" + REPEATED_NAME + "()Ljava/lang/String;", |
| "areturn"); |
| classBuilder = builder.addClass(CLASS_NAME); |
| classBuilder.addMainMethod( |
| buildCodeForVisitingDeclaredMembers( |
| ImmutableList.of( |
| ".limit stack 3", |
| ".limit locals 5", |
| " new " + ANOTHER_CLASS, |
| " dup", |
| " invokespecial " + ANOTHER_CLASS + "/<init>()V", |
| " astore 4", |
| " ldc " + ANOTHER_CLASS, |
| " invokevirtual java/lang/Class/getDeclaredMethods()[Ljava/lang/reflect/Method;"), |
| ImmutableList.of( |
| " aload 4", // instance |
| " aconst_null", |
| " checkcast [Ljava/lang/Object;", |
| " invokevirtual java/lang/reflect/Method/invoke" |
| + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"))); |
| return builder; |
| } |
| |
| @Test |
| public void remainMethodNameConflictInHierarchy_keepRules() throws Exception { |
| JasminBuilder builder = buildMethodNameConflictInHierarchy(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep class " + ANOTHER_CLASS + " {\n" |
| + " <methods>;" |
| + "}\n", |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject sup = codeInspector.clazz(SUPER_CLASS); |
| assertTrue(sup.isPresent()); |
| MethodSubject m1 = sup.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m1.isPresent()); |
| assertFalse(m1.isRenamed()); |
| MethodSubject m2 = sup.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m2.isPresent()); |
| assertFalse(m2.isRenamed()); |
| assertEquals(m1.getFinalName(), m2.getFinalName()); |
| |
| ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS); |
| assertTrue(sub.isPresent()); |
| MethodSubject subM1 = sub.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(subM1.isPresent()); |
| assertFalse(subM1.isRenamed()); |
| MethodSubject subM2 = sub.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(subM2.isPresent()); |
| assertFalse(subM2.isRenamed()); |
| assertEquals(subM1.getFinalName(), subM2.getFinalName()); |
| |
| // No matter what, overloading methods should be renamed to the same name. |
| assertEquals(m1.getFinalName(), subM1.getFinalName()); |
| assertEquals(m2.getFinalName(), subM2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals(javaOutput.stdout, output.stdout); |
| } |
| |
| @Test |
| public void resolveMethodNameConflictInHierarchy_no_options() throws Exception { |
| JasminBuilder builder = buildMethodNameConflictInHierarchy(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| ClassSubject sup = codeInspector.clazz(SUPER_CLASS); |
| assertTrue(sup.isPresent()); |
| MethodSubject m1 = sup.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m1.isPresent()); |
| assertTrue(m1.isRenamed()); |
| MethodSubject m2 = sup.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m2.isPresent()); |
| assertTrue(m2.isRenamed()); |
| assertNotEquals(m1.getFinalName(), m2.getFinalName()); |
| |
| ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS); |
| assertTrue(sub.isPresent()); |
| MethodSubject subM1 = sub.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(subM1.isPresent()); |
| assertTrue(subM1.isRenamed()); |
| MethodSubject subM2 = sub.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(subM2.isPresent()); |
| assertTrue(subM2.isRenamed()); |
| assertNotEquals(subM1.getFinalName(), subM2.getFinalName()); |
| |
| // No matter what, overloading methods should be renamed to the same name. |
| assertEquals(m1.getFinalName(), subM1.getFinalName()); |
| assertEquals(m2.getFinalName(), subM2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals( |
| new HashSet<>(StringUtils.splitLines(javaOutput.stdout)), |
| new HashSet<>(StringUtils.splitLines(output.stdout))); |
| } |
| |
| @Test |
| public void remainMethodNameConflictInHierarchy_overloadaggressively() throws Exception { |
| JasminBuilder builder = buildMethodNameConflictInHierarchy(); |
| ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME); |
| assertEquals(0, javaOutput.exitCode); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| keepMainProguardConfiguration(CLASS_NAME), |
| "-overloadaggressively", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null, backend); |
| |
| CodeInspector codeInspector = new CodeInspector(app); |
| |
| ClassSubject sup = codeInspector.clazz(SUPER_CLASS); |
| assertTrue(sup.isPresent()); |
| MethodSubject m1 = sup.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m1.isPresent()); |
| assertTrue(m1.isRenamed()); |
| MethodSubject m2 = sup.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(m2.isPresent()); |
| assertTrue(m2.isRenamed()); |
| if (backend == Backend.DEX) { |
| assertNotEquals(m1.getFinalName(), m2.getFinalName()); |
| } else { |
| assertEquals(m1.getFinalName(), m2.getFinalName()); |
| } |
| |
| ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS); |
| assertTrue(sub.isPresent()); |
| MethodSubject subM1 = sub.method("java.lang.String", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(subM1.isPresent()); |
| assertTrue(subM1.isRenamed()); |
| MethodSubject subM2 = sub.method("java.lang.Object", REPEATED_NAME, ImmutableList.of()); |
| assertTrue(subM2.isPresent()); |
| assertTrue(subM2.isRenamed()); |
| if (backend == Backend.DEX) { |
| assertNotEquals(subM1.getFinalName(), subM2.getFinalName()); |
| } else { |
| assertEquals(subM1.getFinalName(), subM2.getFinalName()); |
| } |
| |
| // No matter what, overloading methods should be renamed to the same name. |
| assertEquals(m1.getFinalName(), subM1.getFinalName()); |
| assertEquals(m2.getFinalName(), subM2.getFinalName()); |
| |
| ProcessResult output = runRaw(app, CLASS_NAME); |
| assertEquals(0, output.exitCode); |
| assertEquals(javaOutput.stdout, output.stdout); |
| } |
| } |