| // 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.ir.optimize; |
| |
| import com.android.tools.r8.NeverClassInline; |
| import com.android.tools.r8.NeverInline; |
| import com.android.tools.r8.NoVerticalClassMerging; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| /** |
| * Reproduction for issue b/143628636. |
| * |
| * <p>There are a lot of conditions that need to be in place to trigger this issue. The root issue |
| * is resolution incorrectly pruning out a valid candidate. However, mostly code will not even get |
| * to that place as a single target is typically found prior to it, and then, if the incorrect |
| * pruning of the lookup targets takes place, that needs to further cause invalid call site |
| * information to be propagated, which in turn will cause inlining to inline a method where it |
| * should not. It is exceedingly unlikely that this test will continue to be a regression test as |
| * any one of the above aspects could and likely will be changed in the code base. |
| */ |
| @RunWith(Parameterized.class) |
| public class DefaultInterfaceIssue143628636Test extends TestBase { |
| |
| private final TestParameters parameters; |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withAllRuntimes().withAllApiLevels().build(); |
| } |
| |
| public DefaultInterfaceIssue143628636Test(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| @Test |
| public void test() throws Exception { |
| testForR8(parameters.getBackend()) |
| .enableInliningAnnotations() |
| .enableNeverClassInliningAnnotations() |
| .enableNoVerticalClassMergingAnnotations() |
| .addInnerClasses(DefaultInterfaceIssue143628636Test.class) |
| .addKeepMainRule(TestClass.class) |
| .addKeepClassRules(I.class) |
| .setMinApi(parameters.getApiLevel()) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutputLines("2", "5"); |
| } |
| |
| @NoVerticalClassMerging |
| @NeverClassInline |
| public interface A { |
| |
| @NeverInline |
| default void f( |
| // This parameter is needed otherwise the info recording bails out. (See b/143684659). |
| int z) { |
| // In the end the issue will manifest as a class-cast exception because B.g() is inlined here |
| // due to the incorrect conclusion that the only possible receiver type is B. |
| System.out.println(g() + z); |
| } |
| |
| int g(); |
| } |
| |
| // This intermediate interface is needed to cause lookupInterfaceTargets to return null as I |
| // is pinned (why does it return null for a pinned holder is questionable, filed b/143686005). |
| // The return of null, will cause the outer lookup to hit a different lookup case where the |
| // refined receiver (here I) will cause the non-subtype method A.f to be pruned. |
| public interface I extends A {} |
| |
| // Make sure this class and the call to h() are never eliminated. It is the *partial* info |
| // propagated from h() to f() that results in incorrect call-site optimization info. |
| @NoVerticalClassMerging |
| @NeverClassInline |
| public static class B implements A { |
| public final int x; |
| |
| public B(int x) { |
| this.x = x; |
| } |
| |
| @Override |
| public int g() { |
| return x; |
| } |
| |
| @NeverInline |
| public void h() { |
| // This will lookup A.f and propagate that the receiver is known to be B. |
| f(x); |
| } |
| } |
| |
| // Make sure this class and the call to h() are never eliminated. It is the *missing* info |
| // propagated from h() to f() that results in incorrect call-site optimization info. |
| @NoVerticalClassMerging |
| @NeverClassInline |
| public static class C implements I { |
| public final I i; |
| public final int y; |
| |
| public C(I i, int y) { |
| this.i = i; |
| this.y = y; |
| } |
| |
| @Override |
| public int g() { |
| return y; |
| } |
| |
| @NeverInline |
| public void h() { |
| // Due to the refined receiver type I used here, the lookup targets will omit A.f ! |
| // Thus the info propagation does not propagate that I (and thus C) may be a receiver type. |
| i.f(y); |
| } |
| } |
| |
| static class TestClass { |
| |
| public static void main(String[] args) { |
| new B(args.length + 1).h(); |
| new C(args.length == 42 ? null : new C(null, args.length + 2), args.length + 3).h(); |
| } |
| } |
| } |