Add a test for illegal override of final method with horizontal class merging

Bug: 170384891
Change-Id: Ia27a4fe83dc7eeb9f092a7e79c14ec323500eec1
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index d52c4d4..7342124 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -107,7 +108,11 @@
 
   private MethodAccessFlags getAccessFlags() {
     // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
-    return methods.iterator().next().getDefinition().getAccessFlags();
+    MethodAccessFlags flags = methods.iterator().next().getDefinition().getAccessFlags().copy();
+    if (flags.isFinal() && Iterables.any(methods, method -> !method.getAccessFlags().isFinal())) {
+      flags.unsetFinal();
+    }
+    return flags;
   }
 
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java
new file mode 100644
index 0000000..f9eff69
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2020, 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class VirtualMethodMergingOfFinalAndNonFinalMethodTest
+    extends HorizontalClassMergingTestBase {
+
+  public VirtualMethodMergingOfFinalAndNonFinalMethodTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(
+                  inspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(inspector.clazz(C.class), isPresent());
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A.foo()", "B.foo()", "C.foo()");
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    @NeverInline
+    public final void foo() {
+      System.out.println("A.foo()");
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class B {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo()");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends B {
+
+    @NeverInline
+    @Override
+    public void foo() {
+      System.out.println("C.foo()");
+    }
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {
+      new A().foo();
+      new B().foo();
+      new C().foo();
+    }
+  }
+}