| // Copyright (c) 2021, 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.classmerging.horizontal; |
| |
| import com.android.tools.r8.NeverInline; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.classmerging.horizontal.ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.Host.Companion.HostA; |
| import com.android.tools.r8.classmerging.horizontal.ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.Host.Companion.HostB; |
| import com.google.common.collect.ImmutableList; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameter; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest |
| extends TestBase { |
| |
| @Parameter(0) |
| public TestParameters parameters; |
| |
| @Parameter(1) |
| public int thread; |
| |
| @Parameters(name = "{0}, thread: {1}") |
| public static List<Object[]> parameters() { |
| return buildParameters( |
| getTestParameters().withAllRuntimesAndApiLevels().build(), ImmutableList.of(1, 2, 3)); |
| } |
| |
| @Test |
| public void test() throws Exception { |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(getClass()) |
| .addKeepRules( |
| "-keep class " + Main.class.getTypeName() + " {", |
| " public static void thread0();", |
| " public static void thread" + thread + "();", |
| "}") |
| .addHorizontallyMergedClassesInspector( |
| inspector -> |
| inspector |
| .applyIf( |
| thread == 1, i -> i.assertIsCompleteMergeGroup(HostA.class, HostB.class)) |
| .assertNoOtherClassesMerged()) |
| .addOptionsModification( |
| options -> |
| options.horizontalClassMergerOptions().setEnableClassInitializerDeadlockDetection()) |
| .enableInliningAnnotations() |
| .setMinApi(parameters.getApiLevel()) |
| .compile(); |
| } |
| |
| static class Main { |
| |
| // @Keep |
| public static void thread0() { |
| // Takes the lock for Host, then Companion, and then waits to take the lock for HostA. |
| System.out.println(Host.companion.a); |
| System.out.println(Host.companion.b); |
| } |
| |
| // @Keep |
| public static void thread1() { |
| // Ditto. In this case there is no risk of a deadlock, since one of thread0 and thread1 will |
| // take the lock for Host and the other thread will then wait on the lock for Host to be |
| // released. |
| System.out.println(Host.companion.a); |
| System.out.println(Host.companion.b); |
| } |
| |
| // @Keep |
| public static void thread2() { |
| // Takes the lock for Companion, then HostA, and then waits to take the lock for its |
| // superclass Host. |
| System.out.println(new Host.Companion()); |
| } |
| |
| // @Keep |
| public static void thread3() { |
| // Takes the lock for HostA, and then waits to take the lock for its superclass Host. |
| HostA.init(); |
| } |
| } |
| |
| static class Host { |
| |
| static Companion companion = new Companion(); |
| |
| static class Companion { |
| |
| Host a = new HostA(); |
| |
| Host b = new HostB(); |
| |
| static class HostA extends Host { |
| |
| @NeverInline |
| static void init() { |
| System.out.println("HostA.init"); |
| } |
| } |
| |
| static class HostB extends Host {} |
| } |
| } |
| } |