Account for native calls in clinit cycle analysis

Bug: b/341537881
Change-Id: Ia25b58bac14cfd7dd09d2a5ef1cdd3ec7b3fefa9
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
index ec45f9d..7dd373a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
@@ -333,6 +332,8 @@
     }
 
     boolean enqueueMethod(ProgramMethod method) {
+      assert !method.getAccessFlags().isAbstract();
+      assert !method.getAccessFlags().isNative();
       if (seenMethods.add(method)) {
         worklist.addLast(method);
         return true;
@@ -341,6 +342,9 @@
     }
 
     void enqueueTracingRoot(ProgramMethod tracingRoot) {
+      assert tracingRoot.getDefinition().isClassInitializer();
+      assert !tracingRoot.getAccessFlags().isAbstract();
+      assert !tracingRoot.getAccessFlags().isNative();
       boolean added = seenMethods.add(tracingRoot);
       assert added;
       worklist.add(tracingRoot);
@@ -490,12 +494,19 @@
       public void registerInvokeDirect(DexMethod method) {
         DexMethod rewrittenMethod =
             appView.graphLens().lookupInvokeDirect(method, getContext(), codeLens).getReference();
-        MethodResolutionResult resolutionResult =
-            appView().appInfo().resolveMethodOnClassHolderLegacy(rewrittenMethod);
-        if (resolutionResult.isSingleResolution()
-            && resolutionResult.getResolvedHolder().isProgramClass()) {
-          enqueueMethod(resolutionResult.getResolvedProgramMethod());
+        ProgramMethod resolvedMethod =
+            appView()
+                .appInfo()
+                .resolveMethodOnClassHolderLegacy(rewrittenMethod)
+                .getResolvedProgramMethod();
+        if (resolvedMethod == null) {
+          return;
         }
+        if (resolvedMethod.getAccessFlags().isNative()) {
+          fail();
+          return;
+        }
+        enqueueMethod(resolvedMethod);
       }
 
       @Override
@@ -524,10 +535,15 @@
                 .appInfo()
                 .unsafeResolveMethodDueToDexFormatLegacy(rewrittenMethod)
                 .getResolvedProgramMethod();
-        if (resolvedMethod != null) {
-          triggerClassInitializerIfNotAlreadyTriggeredInContext(resolvedMethod.getHolder());
-          enqueueMethod(resolvedMethod);
+        if (resolvedMethod == null) {
+          return;
         }
+        if (resolvedMethod.getAccessFlags().isNative()) {
+          fail();
+          return;
+        }
+        triggerClassInitializerIfNotAlreadyTriggeredInContext(resolvedMethod.getHolder());
+        enqueueMethod(resolvedMethod);
       }
 
       @Override
@@ -537,9 +553,14 @@
         ProgramMethod superTarget =
             asProgramMethodOrNull(
                 appView().appInfo().lookupSuperTarget(rewrittenMethod, getContext(), appView()));
-        if (superTarget != null) {
-          enqueueMethod(superTarget);
+        if (superTarget == null) {
+          return;
         }
+        if (superTarget.getAccessFlags().isNative()) {
+          fail();
+          return;
+        }
+        enqueueMethod(superTarget);
       }
 
       @Override
@@ -554,7 +575,8 @@
         if (resolvedMethod == null) {
           return;
         }
-        if (!resolvedMethod.getHolder().isEffectivelyFinal(appView)) {
+        if (!resolvedMethod.getHolder().isEffectivelyFinal(appView)
+            || resolvedMethod.getAccessFlags().isNative()) {
           fail();
           return;
         }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitCycleAnalysisWithNativeCallTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitCycleAnalysisWithNativeCallTest.java
index 0bc0925..d6ab35d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitCycleAnalysisWithNativeCallTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitCycleAnalysisWithNativeCallTest.java
@@ -3,11 +3,11 @@
 // 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.AssertUtils.assertFailsCompilation;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -27,14 +27,15 @@
 
   @Test
   public void test() throws Exception {
-    // TODO(b/341537881): Account for the native call.
-    assertFailsCompilation(
-        () ->
-            testForR8(parameters.getBackend())
-                .addInnerClasses(getClass())
-                .addKeepMainRule(Main.class)
-                .setMinApi(parameters)
-                .compile());
+    // Checks that compilation succeeds (as opposed to dereferencing the code object of a native
+    // method, as in b/341537881).
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .setMinApi(parameters)
+        .compile();
   }
 
   static class Main {