| // 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.shaking.assumenosideeffects; |
| |
| import static org.junit.Assume.assumeFalse; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.NeverInline; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.StringUtils; |
| import java.util.Collection; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class AssumenosideeffectsPropagationTest extends TestBase { |
| private static final Class<?> MAIN = SubsUser.class; |
| |
| enum TestConfig { |
| SPECIFIC_RULES, |
| NON_SPECIFIC_RULES_PARTIAL, |
| NON_SPECIFIC_RULES_ALL; |
| |
| public String getKeepRules() { |
| switch (this) { |
| case SPECIFIC_RULES: |
| return StringUtils.lines( |
| // Intentionally miss sub types of Base2 for info(). |
| "-assumenosideeffects class **.*$Sub* {", |
| " void info(...); ", |
| "}", |
| // All debug() and verbose() should be removed. |
| "-assumenosideeffects class **.*Sub* {", |
| " void debug(...);", |
| " void verbose(...);", |
| "}" |
| ); |
| case NON_SPECIFIC_RULES_PARTIAL: |
| return StringUtils.lines( |
| // Intentionally miss sub types of Base2 for debug(). |
| "-assumenosideeffects class **.*$Sub* {", |
| " void debug(...);" |
| ,"}", |
| // Targeting info() and verbose() in Base1, without wildcards. |
| "-assumenosideeffects class * extends " + Base1.class.getTypeName() + " {", |
| " void info(...);", |
| " void verbose(...);", |
| "}", |
| // Targeting info() and verbose() in Base2, with wildcards. |
| "-assumenosideeffects class * extends " + Base2.class.getTypeName() + " {", |
| " void *o*(java.lang.String);", |
| "}" |
| ); |
| case NON_SPECIFIC_RULES_ALL: |
| return StringUtils.lines( |
| "-assumenosideeffects class **.*Sub* {", |
| " void *(java.lang.String);", |
| "}" |
| ); |
| default: |
| throw new Unreachable(); |
| } |
| } |
| |
| public String expectedOutput(boolean enableHorizontalClassMerging) { |
| switch (this) { |
| case SPECIFIC_RULES: |
| return enableHorizontalClassMerging |
| ? StringUtils.lines( |
| "[Base2, info]: message08", |
| // Base2#info also has side effects. |
| "[Base2, info]: message4", |
| "The end") |
| : StringUtils.lines( |
| // Itf#info has side effects due to the missing Base2. |
| "[Sub1, info]: message00", |
| "[Base2, info]: message08", |
| // Base2#info also has side effects. |
| "[Base2, info]: message4", |
| "The end"); |
| case NON_SPECIFIC_RULES_PARTIAL: |
| return enableHorizontalClassMerging |
| ? StringUtils.lines( |
| "[AnotherSub2, debug]: message08", "[AnotherSub2, debug]: message5", "The end") |
| : StringUtils.lines( |
| // TODO(b/133208961): Introduce comparison/meet of assume rules. |
| // Itf has side effects for all methods, since we don't compute the meet yet. |
| "[Sub1, info]: message00", |
| "[Base1, debug]: message00", |
| "[Sub1, verbose]: message00", |
| "[Base2, info]: message08", |
| "[AnotherSub2, debug]: message08", |
| "[AnotherSub2, verbose]: message08", |
| // Base2#debug also has side effects. |
| "[AnotherSub2, debug]: message5", |
| "The end"); |
| case NON_SPECIFIC_RULES_ALL: |
| return StringUtils.lines("The end"); |
| default: |
| throw new Unreachable(); |
| } |
| } |
| } |
| |
| private final TestParameters parameters; |
| private final TestConfig config; |
| private final boolean enableHorizontalClassMerging; |
| |
| @Parameterized.Parameters(name = "{0} {1}") |
| public static Collection<Object[]> data() { |
| return buildParameters( |
| getTestParameters().withAllRuntimesAndApiLevels().build(), |
| TestConfig.values(), |
| BooleanUtils.values()); |
| } |
| |
| public AssumenosideeffectsPropagationTest( |
| TestParameters parameters, TestConfig config, boolean enableHorizontalClassMerging) { |
| this.parameters = parameters; |
| this.config = config; |
| this.enableHorizontalClassMerging = enableHorizontalClassMerging; |
| } |
| |
| @Test |
| public void testJVMOutput() throws Exception { |
| assumeTrue(parameters.isCfRuntime()); |
| assumeTrue(config == TestConfig.SPECIFIC_RULES); |
| assumeFalse(enableHorizontalClassMerging); |
| testForJvm() |
| .addTestClasspath() |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutputLines( |
| "[Sub1, info]: message00", |
| "[Base1, debug]: message00", |
| "[Sub1, verbose]: message00", |
| "[Sub1, info]: message1", |
| "[Base1, debug]: message2", |
| "[Sub1, verbose]: message3", |
| "[Base2, info]: message08", |
| "[AnotherSub2, debug]: message08", |
| "[AnotherSub2, verbose]: message08", |
| "[Base2, info]: message4", |
| "[AnotherSub2, debug]: message5", |
| "[AnotherSub2, verbose]: message6", |
| "The end"); |
| } |
| |
| @Test |
| public void testR8() throws Exception { |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(AssumenosideeffectsPropagationTest.class) |
| .addKeepMainRule(MAIN) |
| .addKeepRules(config.getKeepRules()) |
| .addOptionsModification( |
| options -> |
| options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging)) |
| .enableInliningAnnotations() |
| .noMinification() |
| .setMinApi(parameters.getApiLevel()) |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutput(config.expectedOutput(enableHorizontalClassMerging)); |
| } |
| |
| interface Itf { |
| void info(String message); |
| void debug(String message); |
| void verbose(String message); |
| } |
| |
| static abstract class Base1 implements Itf { |
| public abstract void info(String message); |
| public abstract void debug(String message); |
| public abstract void verbose(String message); |
| } |
| |
| static class Sub1 extends Base1 { |
| @Override |
| public void info(String message) { |
| System.out.println("[Sub1, info]: " + message); |
| } |
| |
| @Override |
| public void debug(String message) { |
| System.out.println("[Base1, debug]: " + message); |
| } |
| |
| @Override |
| public void verbose(String message) { |
| System.out.println("[Sub1, verbose]: " + message); |
| } |
| } |
| |
| static class Sub2 extends Base1 { |
| @Override |
| public void info(String message) { |
| System.out.println("[Sub2, info]: " + message); |
| } |
| |
| @Override |
| public void debug(String message) { |
| System.out.println("[Base1, debug]: " + message); |
| } |
| |
| @Override |
| public void verbose(String message) { |
| System.out.println("[Sub2, verbose]: " + message); |
| } |
| } |
| |
| static abstract class Base2 implements Itf { |
| public abstract void info(String message); |
| public abstract void debug(String message); |
| public abstract void verbose(String message); |
| } |
| |
| static class AnotherSub1 extends Base2 { |
| @Override |
| public void info(String message) { |
| System.out.println("[Base2, info]: " + message); |
| } |
| |
| @Override |
| public void debug(String message) { |
| System.out.println("[AnotherSub1, debug]: " + message); |
| } |
| |
| @Override |
| public void verbose(String message) { |
| System.out.println("[AnotherSub1, verbose]: " + message); |
| } |
| } |
| |
| static class AnotherSub2 extends Base2 { |
| @Override |
| public void info(String message) { |
| System.out.println("[Base2, info]: " + message); |
| } |
| |
| @Override |
| public void debug(String message) { |
| System.out.println("[AnotherSub2, debug]: " + message); |
| } |
| |
| @Override |
| public void verbose(String message) { |
| System.out.println("[AnotherSub2, verbose]: " + message); |
| } |
| } |
| |
| static class SubsUser { |
| static Base1 createBase1() { |
| return System.currentTimeMillis() > 0 ? new Sub1() : new Sub2(); |
| } |
| |
| static Base2 createBase2() { |
| return System.currentTimeMillis() < 0 ? new AnotherSub1() : new AnotherSub2(); |
| } |
| |
| @NeverInline |
| private static void testInvokeInterface(Itf itf, String message) { |
| itf.info(message); |
| itf.debug(message); |
| itf.verbose(message); |
| } |
| |
| public static void main(String... args) { |
| Base1 instance1 = createBase1(); |
| testInvokeInterface(instance1, "message00"); |
| instance1.info("message1"); |
| instance1.debug("message2"); |
| instance1.verbose("message3"); |
| Base2 instance2 = createBase2(); |
| testInvokeInterface(instance2, "message08"); |
| instance2.info("message4"); |
| instance2.debug("message5"); |
| instance2.verbose("message6"); |
| System.out.println("The end"); |
| } |
| } |
| } |