| // 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.addconfigurationdebugging; |
| |
| import static com.android.tools.r8.graph.DexEncodedMethod.CONFIGURATION_DEBUGGING_PREFIX; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.R8FullTestBuilder; |
| import com.android.tools.r8.R8TestRunResult; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.InstructionSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import java.nio.file.Path; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| class BaseClass { |
| Object field; |
| BaseClass(Object usedArg) { |
| field = usedArg; |
| } |
| } |
| |
| class UninstantiatedClass extends BaseClass { |
| UninstantiatedClass() { |
| super(null); |
| System.out.println("UninstantiatedClass#<init>"); |
| } |
| |
| UninstantiatedClass(String arg) { |
| super(arg); |
| System.out.println("UninstantiatedClass#<init>(String)"); |
| } |
| } |
| |
| class TestClass { |
| BaseClass b; |
| |
| TestClass() { |
| b = new BaseClass(this); |
| System.out.println(b); |
| } |
| |
| void foo(int i, long l) { |
| System.out.println("void TestClass#foo(IJ)"); |
| } |
| |
| static void bar(TestClass arg) { |
| System.out.println("void TestClass#bar(TestClass)"); |
| } |
| } |
| |
| class Caller { |
| public static void main(String[] args) { |
| try { |
| new UninstantiatedClass(); |
| } catch (RuntimeException e) { |
| } |
| try { |
| new UninstantiatedClass("aaarrrrrrhhhhhh"); |
| } catch (RuntimeException e) { |
| } |
| |
| TestClass instance = new TestClass(); |
| try { |
| instance.foo(4, 2L); |
| } catch (RuntimeException e) { |
| } |
| try { |
| TestClass.bar(instance); |
| } catch (RuntimeException e) { |
| } |
| |
| throw new RuntimeException("Reaching the end"); |
| } |
| } |
| |
| @RunWith(Parameterized.class) |
| public class ConfigurationDebuggingTest extends TestBase { |
| private static final String PACKAGE_NAME = |
| ConfigurationDebuggingTest.class.getPackage().getName(); |
| |
| private final TestParameters parameters; |
| |
| @Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withAllRuntimes().build(); |
| } |
| |
| public ConfigurationDebuggingTest(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| @Test |
| public void test() throws Exception { |
| Path firstRunArchive = |
| testForR8(parameters.getBackend()) |
| .addProgramClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class) |
| .addKeepRules("-addconfigurationdebugging") |
| .addKeepRules("-keep class **.TestClass { <init>(); }") |
| .noMinification() |
| .setMinApi(parameters.getRuntime()) |
| .compile() |
| .inspect(this::inspect) |
| .writeToZip(); |
| |
| R8FullTestBuilder builder = |
| testForR8(parameters.getBackend()) |
| .addLibraryClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class) |
| .addLibraryFiles(ToolHelper.getDefaultAndroidJar()) |
| .addProgramClasses(Caller.class) |
| .addKeepMainRule(Caller.class) |
| .setMinApi(parameters.getRuntime()); |
| R8TestRunResult result = |
| builder |
| .compile() |
| .addRunClasspathFiles(firstRunArchive) |
| .run(parameters.getRuntime(), Caller.class); |
| // TODO(b/117302947): Dex runtime should be able to find that framework class. |
| if (parameters.isDexRuntime()) { |
| result.assertFailureWithErrorThatMatches(containsString("NoClassDefFoundError")); |
| result.assertFailureWithErrorThatMatches(containsString("android.util.Log")); |
| return; |
| } |
| result |
| .assertFailureWithErrorThatMatches( |
| containsString(createExpectedMessage(UninstantiatedClass.class))) |
| .assertFailureWithErrorThatMatches(containsString("void <init>()")) |
| .assertFailureWithErrorThatMatches(containsString("void <init>(java.lang.String)")) |
| .assertFailureWithErrorThatMatches( |
| containsString(createExpectedMessage(TestClass.class))) |
| .assertFailureWithErrorThatMatches(containsString("void foo(int,long)")) |
| .assertFailureWithErrorThatMatches( |
| containsString("void bar(" + PACKAGE_NAME + ".TestClass" +")")) |
| .assertFailureWithErrorThatMatches(containsString("Reaching the end")); |
| } |
| |
| private String createExpectedMessage(Class<?> clazz) { |
| return CONFIGURATION_DEBUGGING_PREFIX + clazz.getName(); |
| } |
| |
| private void inspect(CodeInspector inspector) { |
| ClassSubject baseClass = inspector.clazz(BaseClass.class); |
| assertThat(baseClass, isPresent()); |
| baseClass.allMethods().forEach(methodSubject -> { |
| assertTrue(methodSubject.isInstanceInitializer()); |
| assertFalse(hasThrow(methodSubject)); |
| }); |
| |
| ClassSubject uninstantiatedClass = inspector.clazz(UninstantiatedClass.class); |
| assertThat(uninstantiatedClass, isPresent()); |
| uninstantiatedClass.allMethods().forEach(methodSubject -> { |
| assertTrue(methodSubject.isInstanceInitializer()); |
| assertTrue(hasThrow(methodSubject)); |
| }); |
| |
| ClassSubject testClass = inspector.clazz(TestClass.class); |
| assertThat(testClass, isPresent()); |
| MethodSubject foo = testClass.uniqueMethodWithName("foo"); |
| assertThat(foo, isPresent()); |
| assertTrue(hasThrow(foo)); |
| MethodSubject bar = testClass.uniqueMethodWithName("bar"); |
| assertThat(bar, isPresent()); |
| assertTrue(hasThrow(bar)); |
| } |
| |
| private static boolean hasThrow(MethodSubject methodSubject) { |
| return methodSubject.iterateInstructions(InstructionSubject::isThrow).hasNext(); |
| } |
| } |