|  | // 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.signature; | 
|  |  | 
|  | import static org.junit.Assert.assertThrows; | 
|  | import static org.junit.Assume.assumeTrue; | 
|  |  | 
|  | import com.android.tools.r8.CompilationMode; | 
|  | import com.android.tools.r8.R8TestBuilder; | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.TestParameters; | 
|  | import com.android.tools.r8.TestParametersCollection; | 
|  | import com.android.tools.r8.shaking.ProguardKeepAttributes; | 
|  | import java.lang.reflect.ParameterizedType; | 
|  | import java.lang.reflect.Type; | 
|  | import java.lang.reflect.TypeVariable; | 
|  | 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 GenericSignatureRenamingTest extends TestBase { | 
|  |  | 
|  | private final TestParameters parameters; | 
|  |  | 
|  | @Parameters(name = "{0}") | 
|  | public static TestParametersCollection data() { | 
|  | return getTestParameters().withAllRuntimesAndApiLevels().build(); | 
|  | } | 
|  |  | 
|  | public GenericSignatureRenamingTest(TestParameters parameters) { | 
|  | this.parameters = parameters; | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testJVM() throws Exception { | 
|  | assumeTrue(parameters.isCfRuntime()); | 
|  | testForJvm().addTestClasspath().run(parameters.getRuntime(), Main.class).assertSuccess(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testD8() throws Exception { | 
|  | assumeTrue(parameters.isDexRuntime()); | 
|  | testForD8() | 
|  | .addProgramClasses(Main.class) | 
|  | .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class) | 
|  | .setMode(CompilationMode.RELEASE) | 
|  | .setMinApi(parameters.getApiLevel()) | 
|  | .compile() | 
|  | .assertNoMessages() | 
|  | .run(parameters.getRuntime(), Main.class) | 
|  | .assertSuccess(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testR8() throws Exception { | 
|  | test(testForR8(parameters.getBackend())); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testR8Compat() throws Exception { | 
|  | test(testForR8Compat(parameters.getBackend())); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testR8NoMinify() throws Exception { | 
|  | test(testForR8(parameters.getBackend()).addKeepRules("-dontobfuscate")); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testR8WithAssertEnabled() { | 
|  | // TODO(b/154793333): Enable assertions always when resolved. | 
|  | assertThrows( | 
|  | AssertionError.class, | 
|  | () -> { | 
|  | test( | 
|  | testForR8(parameters.getBackend()) | 
|  | .addKeepRules("-dontobfuscate") | 
|  | .addOptionsModification( | 
|  | internalOptions -> | 
|  | internalOptions.testing.assertConsistentRenamingOfSignature = true)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | private void test(R8TestBuilder<?> builder) throws Exception { | 
|  | builder | 
|  | .addKeepRules("-dontoptimize") | 
|  | .addKeepAttributes(ProguardKeepAttributes.SIGNATURE) | 
|  | .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES) | 
|  | .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD) | 
|  | .addKeepMainRule(Main.class) | 
|  | .addProgramClasses(Main.class) | 
|  | .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class) | 
|  | .setMode(CompilationMode.RELEASE) | 
|  | .setMinApi(parameters.getApiLevel()) | 
|  | .compile() | 
|  | .assertNoMessages() | 
|  | .run(parameters.getRuntime(), Main.class) | 
|  | .assertSuccess(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class A<T> { | 
|  | class Y { | 
|  |  | 
|  | class YY {} | 
|  |  | 
|  | class ZZ extends YY { | 
|  | public YY yy; | 
|  |  | 
|  | YY newYY() { | 
|  | return new YY(); | 
|  | } | 
|  | } | 
|  |  | 
|  | ZZ zz() { | 
|  | return new ZZ(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class GenericInner<S extends T> { | 
|  |  | 
|  | private S s; | 
|  |  | 
|  | public GenericInner(S s) { | 
|  | this.s = s; | 
|  | } | 
|  | } | 
|  |  | 
|  | class Z extends Y {} | 
|  |  | 
|  | static class S {} | 
|  |  | 
|  | Y newY() { | 
|  | return new Y(); | 
|  | } | 
|  |  | 
|  | Z newZ() { | 
|  | return new Z(); | 
|  | } | 
|  |  | 
|  | Y.ZZ newZZ() { | 
|  | return new Y().zz(); | 
|  | } | 
|  |  | 
|  | public <S extends T> GenericInner<S> create(S s) { | 
|  | return new GenericInner<>(s); | 
|  | } | 
|  | } | 
|  |  | 
|  | class B<T extends A<T>> {} | 
|  |  | 
|  | class CY<T extends A<T>.Y> {} | 
|  |  | 
|  | class CYY<T extends A<T>.Y.YY> {} | 
|  |  | 
|  | class Main { | 
|  |  | 
|  | private static void check(boolean b, String message) { | 
|  | if (!b) { | 
|  | throw new RuntimeException("Check failed: " + message); | 
|  | } | 
|  | } | 
|  |  | 
|  | public static void main(String[] args) { | 
|  | A.Z z = new A().newZ(); | 
|  | A.Y.YY yy = new A().newZZ().yy; | 
|  |  | 
|  | B b = new B(); | 
|  | CY cy = new CY(); | 
|  |  | 
|  | CYY cyy = new CYY(); | 
|  | A.S s = new A.S(); | 
|  |  | 
|  | A<Object>.GenericInner<String> foo = new A<Object>().create("Foo"); | 
|  | Class<? extends A.GenericInner> aClass = foo.getClass(); | 
|  |  | 
|  | // Check if names of Z and ZZ shows A as a superclass. | 
|  | Class classA = A.class; | 
|  | String nameA = classA.getName(); | 
|  |  | 
|  | TypeVariable[] v = classA.getTypeParameters(); | 
|  | check(v != null && v.length == 1, classA + " expected to have 1 type parameter."); | 
|  |  | 
|  | Class classZ = new A().newZ().getClass(); | 
|  | String nameZ = classZ.getName(); | 
|  | check(nameZ.startsWith(nameA + "$"), nameZ + " expected to start with " + nameA + "$."); | 
|  |  | 
|  | Class classZZ = new A().newZZ().getClass(); | 
|  | String nameZZ = classZZ.getName(); | 
|  | check(nameZZ.startsWith(nameA + "$"), nameZZ + " expected to start with " + nameA + "$."); | 
|  |  | 
|  | // Check that the owner of the superclass of Z is A. | 
|  | Class ownerClassOfSuperOfZ = getEnclosingClass(classZ.getGenericSuperclass()); | 
|  |  | 
|  | check( | 
|  | ownerClassOfSuperOfZ == A.class, | 
|  | ownerClassOfSuperOfZ + " expected to be equal to " + A.class); | 
|  |  | 
|  | // Check that the owner-owner of the superclass of Z is A. | 
|  | Class ownerOfOwnerOfSuperOfZZ = | 
|  | getEnclosingClass(classZZ.getGenericSuperclass()).getEnclosingClass(); | 
|  |  | 
|  | check( | 
|  | ownerOfOwnerOfSuperOfZZ == A.class, | 
|  | ownerOfOwnerOfSuperOfZZ + " expected to be equal to " + A.class); | 
|  | } | 
|  |  | 
|  | private static Class getEnclosingClass(Type type) { | 
|  | if (type instanceof ParameterizedType) { | 
|  | // On the JVM it's a ParameterizedType. | 
|  | return (Class) ((ParameterizedType) ((ParameterizedType) type).getOwnerType()).getRawType(); | 
|  | } else { | 
|  | // On the ART it's Class. | 
|  | check(type instanceof Class, type + " expected to be a ParameterizedType or Class."); | 
|  | return ((Class) type).getEnclosingClass(); | 
|  | } | 
|  | } | 
|  | } |