|  | // 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.codeinspector.Matchers.isPresent; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; | 
|  | import static org.hamcrest.CoreMatchers.not; | 
|  | import static org.hamcrest.MatcherAssert.assertThat; | 
|  | import static org.junit.Assert.assertEquals; | 
|  |  | 
|  | import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | 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 java.util.ArrayList; | 
|  | import java.util.Collection; | 
|  | import java.util.List; | 
|  | 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; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class IfOnClassTest extends ProguardCompatibilityTestBase { | 
|  | private static final List<Class<?>> CLASSES = | 
|  | ImmutableList.of( | 
|  | EmptyMainClassForIfOnClassTests.class, | 
|  | Precondition.class, | 
|  | DependentUser.class, | 
|  | Dependent.class); | 
|  |  | 
|  | private final Shrinker shrinker; | 
|  | private final boolean keepPrecondition; | 
|  |  | 
|  | public IfOnClassTest(Shrinker shrinker, boolean keepPrecondition) { | 
|  | this.shrinker = shrinker; | 
|  | this.keepPrecondition = keepPrecondition; | 
|  | } | 
|  |  | 
|  | @Parameters(name = "shrinker: {0} precondition: {1}") | 
|  | public static Collection<Object[]> data() { | 
|  | return ImmutableList.of( | 
|  | new Object[] {Shrinker.PROGUARD6, true}, | 
|  | new Object[] {Shrinker.PROGUARD6, false}, | 
|  | new Object[] {Shrinker.R8, true}, | 
|  | new Object[] {Shrinker.R8, false}, | 
|  | new Object[] {Shrinker.R8_CF, true}, | 
|  | new Object[] {Shrinker.R8_CF, false}); | 
|  | } | 
|  |  | 
|  | private String adaptConfiguration(String proguardConfig) { | 
|  | List<String> configWithPrecondition = new ArrayList<>(); | 
|  | configWithPrecondition.add( | 
|  | keepMainProguardConfiguration(EmptyMainClassForIfOnClassTests.class)); | 
|  | if (keepPrecondition) { | 
|  | configWithPrecondition.add("-keep class **.Precondition"); | 
|  | } | 
|  | configWithPrecondition.add(proguardConfig); | 
|  | return String.join(System.lineSeparator(), configWithPrecondition); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected CodeInspector inspectR8Result( | 
|  | List<Class<?>> programClasses, | 
|  | String proguardConfig, | 
|  | Consumer<InternalOptions> configure, | 
|  | Backend backend) | 
|  | throws Exception { | 
|  | return super.inspectR8Result( | 
|  | programClasses, adaptConfiguration(proguardConfig), configure, backend); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected CodeInspector inspectProguard5Result( | 
|  | List<Class<?>> programClasses, String proguardConfig) throws Exception { | 
|  | return super.inspectProguard5Result(programClasses, adaptConfiguration(proguardConfig)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected CodeInspector inspectProguard6Result( | 
|  | List<Class<?>> programClasses, String proguardConfig) throws Exception { | 
|  | return super.inspectProguard6Result(programClasses, adaptConfiguration(proguardConfig)); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeep_withoutNthWildcard() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | "-if class **.Precondition", | 
|  | "-keep,allowobfuscation class **.*User", | 
|  | "-if class **.DependentUser", | 
|  | "-keep,allowobfuscation class **.Dependent" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  | if (!keepPrecondition) { | 
|  | // TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary. | 
|  | if (shrinker == Shrinker.PROGUARD6) { | 
|  | return; | 
|  | } | 
|  | assertEquals(1, codeInspector.allClasses().size()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(DependentUser.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | // Members of DependentUser are not used anywhere. | 
|  | MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of()); | 
|  | assertThat(m, not(isPresent())); | 
|  | FieldSubject f = clazz.field("int", "canBeShrinked"); | 
|  | assertThat(f, not(isPresent())); | 
|  |  | 
|  | // Although DependentUser#callFoo is shrinked, Dependent is kept via -if. | 
|  | clazz = codeInspector.clazz(Dependent.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | // But, its members are gone. | 
|  | m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | assertThat(m, not(isPresent())); | 
|  | f = clazz.field("int", "intField"); | 
|  | assertThat(f, not(isPresent())); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeep_withNthWildcard() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | "-if class **.Precondition", | 
|  | "-keep,allowobfuscation class **.*User", | 
|  | "-if class **.*User", | 
|  | "-keep,allowobfuscation class <1>.<2>" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  | if (!keepPrecondition) { | 
|  | // TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary. | 
|  | if (shrinker == Shrinker.PROGUARD6) { | 
|  | return; | 
|  | } | 
|  | assertEquals(1, codeInspector.allClasses().size()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(DependentUser.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | // Members of DependentUser are not used anywhere. | 
|  | MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of()); | 
|  | assertThat(m, not(isPresent())); | 
|  | FieldSubject f = clazz.field("int", "canBeShrinked"); | 
|  | assertThat(f, not(isPresent())); | 
|  |  | 
|  | // Although DependentUser#callFoo is shrinked, Dependent is kept via -if. | 
|  | clazz = codeInspector.clazz(Dependent.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | // But, its members are gone. | 
|  | m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | assertThat(m, not(isPresent())); | 
|  | f = clazz.field("int", "intField"); | 
|  | assertThat(f, not(isPresent())); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeepClassesWithMembers() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | "-if class **.Precondition", | 
|  | "-keepclasseswithmembers,allowobfuscation class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  | if (!keepPrecondition) { | 
|  | assertEquals(1, codeInspector.allClasses().size()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(DependentUser.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of()); | 
|  | assertThat(m, isPresentAndRenamed()); | 
|  | FieldSubject f = clazz.field("int", "canBeShrinked"); | 
|  | assertThat(f, not(isPresent())); | 
|  |  | 
|  | // Dependent is kept due to DependentUser#callFoo, but renamed. | 
|  | clazz = codeInspector.clazz(Dependent.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | assertThat(m, isPresentAndRenamed()); | 
|  | f = clazz.field("int", "intField"); | 
|  | assertThat(f, isPresentAndRenamed()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeepClassMembers_withoutNthWildcard() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | "-if class **.Precondition", | 
|  | "-keepclassmembers,allowobfuscation class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}", | 
|  | // If the member is kept, keep the enclosing class too. | 
|  | "-if class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}", | 
|  | "-keep,allowobfuscation class **.*User" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  | if (!keepPrecondition) { | 
|  | // TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary. | 
|  | if (shrinker == Shrinker.PROGUARD6) { | 
|  | return; | 
|  | } | 
|  | assertEquals(1, codeInspector.allClasses().size()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (shrinker.isR8()) { | 
|  | // The -keepclassmembers rule should not keep Dependent nor DependentUser since DependentUser | 
|  | // is never referenced. | 
|  | assertThat(codeInspector.clazz(EmptyMainClassForIfOnClassTests.class), isPresent()); | 
|  | assertThat(codeInspector.clazz(Precondition.class), isPresent()); | 
|  | assertEquals(2, codeInspector.allClasses().size()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(DependentUser.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of()); | 
|  | assertThat(m, isPresentAndRenamed()); | 
|  | FieldSubject f = clazz.field("int", "canBeShrinked"); | 
|  | assertThat(f, not(isPresent())); | 
|  |  | 
|  | // Dependent is kept due to DependentUser#callFoo, but renamed. | 
|  | clazz = codeInspector.clazz(Dependent.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | assertThat(m, isPresentAndRenamed()); | 
|  | f = clazz.field("int", "intField"); | 
|  | assertThat(f, isPresentAndRenamed()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeepClassMembers_withNthWildcard() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | "-if class **.Precondition", | 
|  | "-keepclassmembers,allowobfuscation class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}", | 
|  | // If the member is kept, keep the enclosing class too. | 
|  | "-if class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}", | 
|  | "-keep,allowobfuscation class <1>.<2>User" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  | if (!keepPrecondition) { | 
|  | // TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary. | 
|  | if (shrinker == Shrinker.PROGUARD6) { | 
|  | return; | 
|  | } | 
|  | assertEquals(1, codeInspector.allClasses().size()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (shrinker.isR8()) { | 
|  | // The -keepclassmembers rule should not keep Dependent nor DependentUser since DependentUser | 
|  | // is never referenced. | 
|  | assertThat(codeInspector.clazz(EmptyMainClassForIfOnClassTests.class), isPresent()); | 
|  | assertThat(codeInspector.clazz(Precondition.class), isPresent()); | 
|  | assertEquals(2, codeInspector.allClasses().size()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(DependentUser.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of()); | 
|  | assertThat(m, isPresentAndRenamed()); | 
|  | FieldSubject f = clazz.field("int", "canBeShrinked"); | 
|  | assertThat(f, not(isPresent())); | 
|  |  | 
|  | // Dependent is kept due to DependentUser#callFoo, but renamed. | 
|  | clazz = codeInspector.clazz(Dependent.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | assertThat(m, isPresentAndRenamed()); | 
|  | f = clazz.field("int", "intField"); | 
|  | assertThat(f, isPresentAndRenamed()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeepNames() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | // To keep DependentUser#callFoo, which in turn kept Dependent#<init> as well. | 
|  | // We're testing renaming of Dependent itself and members. | 
|  | "-keepclasseswithmembers,allowobfuscation class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}", | 
|  | "-if class **.Precondition", | 
|  | "-keepnames class **.Dependent" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(Dependent.class); | 
|  | // Only class name is not renamed, if triggered. | 
|  | assertThat(clazz, keepPrecondition ? isPresentAndNotRenamed() : isPresentAndRenamed()); | 
|  | MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | assertThat(m, isPresentAndRenamed()); | 
|  | FieldSubject f = clazz.field("int", "intField"); | 
|  | assertThat(f, isPresentAndRenamed()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeepClassesWithMemberNames() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | // To keep DependentUser#callFoo, which in turn kept Dependent#<init> as well. | 
|  | // We're testing renaming of Dependent itself and members. | 
|  | "-keepclasseswithmembers,allowobfuscation class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}", | 
|  | "-if class **.Precondition", | 
|  | "-keepclasseswithmembernames class **.Dependent {", | 
|  | "  <methods>;", | 
|  | "}" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(Dependent.class); | 
|  | // Class name is not renamed, if triggered. | 
|  | assertThat(clazz, keepPrecondition ? isPresentAndNotRenamed() : isPresentAndRenamed()); | 
|  | MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | // Method name is not renamed either, if triggered. | 
|  | assertThat(m, keepPrecondition ? isPresentAndNotRenamed() : isPresentAndRenamed()); | 
|  | FieldSubject f = clazz.field("int", "intField"); | 
|  | assertThat(f, isPresentAndRenamed()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void ifThenKeepClassMemberNames() throws Exception { | 
|  | List<String> config = ImmutableList.of( | 
|  | // To keep DependentUser#callFoo, which in turn kept Dependent#<init> as well. | 
|  | // We're testing renaming of Dependent itself and members. | 
|  | "-keepclasseswithmembers,allowobfuscation class **.*User {", | 
|  | "  static void callFoo(...);", | 
|  | "}", | 
|  | "-if class **.Precondition", | 
|  | "-keepclassmembernames class **.Dependent {", | 
|  | "  <methods>;", | 
|  | "}" | 
|  | ); | 
|  |  | 
|  | CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config); | 
|  |  | 
|  | ClassSubject clazz = codeInspector.clazz(Dependent.class); | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of()); | 
|  | // Only method name is not renamed, if triggered. | 
|  | assertThat(m, keepPrecondition ? isPresentAndNotRenamed() : isPresentAndRenamed()); | 
|  | FieldSubject f = clazz.field("int", "intField"); | 
|  | assertThat(f, isPresentAndRenamed()); | 
|  | } | 
|  | } |