Fail compilation for invokes to <clinit>

Change-Id: Id0c9e2bbd957cb2f782e301ef7ca3383e58a3402
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index a520ae3..174dca5 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -62,6 +62,7 @@
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -125,31 +126,35 @@
   @Override
   public CfCode asCfCode() {
     if (code == null) {
-      ReparseContext context = this.context;
-      JarApplicationReader application = this.application;
-      assert application != null;
-      assert context != null;
-      // The ClassCodeVisitor is in charge of setting this.context to null.
-      try {
-        parseCode(context, false);
-      } catch (JsrEncountered e) {
-        for (Code code : context.codeList) {
-          code.asLazyCfCode().code = null;
-          code.asLazyCfCode().context = context;
-          code.asLazyCfCode().application = application;
-        }
-        try {
-          parseCode(context, true);
-        } catch (JsrEncountered e1) {
-          throw new Unreachable(e1);
-        }
-      }
-      assert verifyNoReparseContext(context.owner);
+      ExceptionUtils.withOriginAttachmentHandler(origin, this::internalParseCode);
     }
     assert code != null;
     return code;
   }
 
+  private void internalParseCode() {
+    ReparseContext context = this.context;
+    JarApplicationReader application = this.application;
+    assert application != null;
+    assert context != null;
+    // The ClassCodeVisitor is in charge of setting this.context to null.
+    try {
+      parseCode(context, false);
+    } catch (JsrEncountered e) {
+      for (Code code : context.codeList) {
+        code.asLazyCfCode().code = null;
+        code.asLazyCfCode().context = context;
+        code.asLazyCfCode().application = application;
+      }
+      try {
+        parseCode(context, true);
+      } catch (JsrEncountered e1) {
+        throw new Unreachable(e1);
+      }
+    }
+    assert verifyNoReparseContext(context.owner);
+  }
+
   @Override
   public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
     return asCfCode().getCodeAsInlining(caller, callee);
@@ -773,6 +778,9 @@
     @Override
     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
       DexMethod method = application.getMethod(owner, name, desc);
+      if (application.getFactory().isClassConstructor(method)) {
+        throw new CompilationError("Invalid input code with a call to <clinit>");
+      }
       instructions.add(new CfInvoke(opcode, method, itf));
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index a66d383..9311d46 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -51,6 +51,10 @@
     this.ignoreDexInArchive = ignoreDexInArchive;
   }
 
+  public Origin getOrigin() {
+    return origin;
+  }
+
   private List<ProgramResource> readArchive() throws IOException {
     List<ProgramResource> dexResources = new ArrayList<>();
     List<ProgramResource> classResources = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
index 6517f6e..a826739 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -58,15 +58,20 @@
   }
 
   public static Matcher<Diagnostic> diagnosticOrigin(Origin origin) {
+    return diagnosticOrigin(CoreMatchers.is(origin));
+  }
+
+  public static Matcher<Diagnostic> diagnosticOrigin(Matcher<Origin> originMatcher) {
     return new DiagnosticsMatcher() {
       @Override
       protected boolean eval(Diagnostic diagnostic) {
-        return diagnostic.getOrigin().equals(origin);
+        return originMatcher.matches(diagnostic.getOrigin());
       }
 
       @Override
       protected void explain(Description description) {
-        description.appendText("origin ").appendText(origin.toString());
+        description.appendText("origin with ");
+        originMatcher.describeTo(description);
       }
     };
   }
diff --git a/src/test/java/com/android/tools/r8/OriginMatcher.java b/src/test/java/com/android/tools/r8/OriginMatcher.java
new file mode 100644
index 0000000..8484ea3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/OriginMatcher.java
@@ -0,0 +1,33 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public abstract class OriginMatcher extends TypeSafeMatcher<Origin> {
+
+  public static Matcher<Origin> hasParent(Origin parent) {
+    return new OriginMatcher() {
+      @Override
+      protected boolean matchesSafely(Origin origin) {
+        Origin current = origin;
+        do {
+          if (current == parent) {
+            return true;
+          }
+          current = current.parent();
+        } while (current != null);
+        return false;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("not a parent " + parent);
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/InvokeClinitTest.java b/src/test/java/com/android/tools/r8/cf/InvokeClinitTest.java
new file mode 100644
index 0000000..568fff6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/InvokeClinitTest.java
@@ -0,0 +1,104 @@
+// 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.cf;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeClinitTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public InvokeClinitTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(A.class)
+        .addProgramClassFileData(transformMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(
+            anyOf(
+                containsString(ClassFormatError.class.getSimpleName()),
+                containsString(VerifyError.class.getSimpleName())));
+  }
+
+  @Test
+  public void testD8() {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8(parameters.getBackend())
+                .addProgramClasses(A.class)
+                .addProgramClassFileData(transformMain())
+                .setMinApi(parameters.getApiLevel())
+                .compile());
+  }
+
+  @Test
+  public void testR8() {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(A.class)
+                .addProgramClassFileData(transformMain())
+                .addKeepMainRule(Main.class)
+                .setMinApi(parameters.getApiLevel())
+                .compile());
+  }
+
+  private byte[] transformMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, continuation) ->
+                continuation.visitMethodInsn(opcode, owner, "<clinit>", descriptor, isInterface))
+        .transform();
+  }
+
+  static class A {
+    static {
+      System.out.println("A.<clinit>");
+    }
+
+    static void willBeClinit() {
+      System.out.println("unused");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.willBeClinit();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
index ee5142f..d1a4d35 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.OriginMatcher.hasParent;
 import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
@@ -23,7 +24,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -141,28 +142,23 @@
     } else {
       assertThrows(
           CompilationFailedException.class,
-          () ->
-              testForD8(parameters.getBackend())
-                  .addProgramFiles(testClasses.getInstrumented())
-                  .addProgramFiles(ToolHelper.JACOCO_AGENT)
-                  .setMinApi(parameters.getApiLevel())
-                  .compileWithExpectedDiagnostics(
-                      diagnostics -> {
-                        // Check that the error is reported as an error to the diagnostics handler.
-                        diagnostics.assertErrorsCount(1);
-                        diagnostics.assertAllErrorsMatch(
-                            allOf(
-                                diagnosticMessage(containsString("Unsupported dynamic constant")),
-                                // The fatal error is not given an origin, so it can't provide it.
-                                // Note: This could be fixed by delaying reporting and associate the
-                                // info
-                                //  at the top-level handler. It would require mangling of the
-                                // diagnostic,
-                                //  so maybe not that elegant.
-                                diagnosticOrigin(Origin.unknown())));
-                        diagnostics.assertWarningsCount(0);
-                        diagnostics.assertInfosCount(0);
-                      }));
+          () -> {
+            ArchiveResourceProvider provider =
+                ArchiveResourceProvider.fromArchive(testClasses.getInstrumented(), true);
+            testForD8(parameters.getBackend())
+                .addProgramResourceProviders(provider)
+                .addProgramFiles(ToolHelper.JACOCO_AGENT)
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      // Check that the error is reported as an error to the diagnostics handler.
+                      diagnostics.assertOnlyErrors();
+                      diagnostics.assertErrorsMatch(
+                          allOf(
+                              diagnosticMessage(containsString("Unsupported dynamic constant")),
+                              diagnosticOrigin(hasParent(provider.getOrigin()))));
+                    });
+          });
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
deleted file mode 100644
index b15d446..0000000
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-// 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.resolution.interfacetargets;
-
-import static org.hamcrest.core.StringContains.containsString;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.transformers.ClassTransformer;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
-import org.hamcrest.CoreMatchers;
-import org.hamcrest.Matcher;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-import org.objectweb.asm.MethodVisitor;
-
-@RunWith(Parameterized.class)
-public class InvokeInterfaceClInitTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  public InvokeInterfaceClInitTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testResolution() throws Exception {
-    assumeTrue(parameters.useRuntimeAsNoneRuntime());
-    AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClasses(A.class, B.class)
-                .addClassProgramData(transformI(), transformMain())
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
-    AppInfoWithLiveness appInfo = appView.appInfo();
-    DexMethod method = buildNullaryVoidMethod(I.class, "<clinit>", appInfo.dexItemFactory());
-    DexProgramClass context =
-        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    Assert.assertThrows(
-        AssertionError.class,
-        () ->
-            appInfo
-                .resolveMethodOnInterface(method)
-                .lookupVirtualDispatchTargets(context, appInfo));
-  }
-
-  private Matcher<String> getExpected() {
-    if (parameters.getRuntime().isCf()) {
-      Matcher<String> expected = containsString("java.lang.VerifyError");
-      // JDK 9 and 11 output VerifyError or ClassFormatError non-deterministically.
-      if (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
-        expected = CoreMatchers.anyOf(expected, containsString("java.lang.ClassFormatError"));
-      }
-      return expected;
-    }
-    assert parameters.getRuntime().isDex();
-    DexRuntime dexRuntime = parameters.getRuntime().asDex();
-    if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-      return containsString("NoSuchMethodError");
-    }
-    return containsString("java.lang.VerifyError");
-  }
-
-  @Test
-  public void testRuntimeClInit()
-      throws IOException, CompilationFailedException, ExecutionException {
-    testForRuntime(parameters)
-        .addProgramClasses(A.class, B.class)
-        .addProgramClassFileData(transformMain(), transformI())
-        .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpected());
-  }
-
-  @Test
-  public void testR8ClInit() throws IOException, CompilationFailedException, ExecutionException {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(A.class, B.class)
-        .addProgramClassFileData(transformMain(), transformI())
-        .addKeepMainRule(Main.class)
-        .setMinApi(parameters.getApiLevel())
-        .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpected());
-  }
-
-  private byte[] transformI() throws IOException {
-    return transformer(I.class)
-        .addClassTransformer(
-            new ClassTransformer() {
-              @Override
-              public MethodVisitor visitMethod(
-                  int access,
-                  String name,
-                  String descriptor,
-                  String signature,
-                  String[] exceptions) {
-                return super.visitMethod(
-                    access | Constants.ACC_STATIC, "<clinit>", descriptor, signature, exceptions);
-              }
-            })
-        .transform();
-  }
-
-  private byte[] transformMain() throws IOException {
-    return transformer(Main.class)
-        .transformMethodInsnInMethod(
-            "callClInit",
-            (opcode, owner, name, descriptor, isInterface, continuation) ->
-                continuation.visitMethodInsn(opcode, owner, "<clinit>", descriptor, isInterface))
-        .transform();
-  }
-
-  public interface I {
-
-    default void foo() { // <-- will be rewritten to <clinit>
-      System.out.println("I.foo");
-    }
-  }
-
-  public static class A implements I {}
-
-  public static class B implements I {}
-
-  public static class Main {
-
-    public static void main(String[] args) {
-      callClInit(args.length == 0 ? new A() : new B());
-    }
-
-    private static void callClInit(I i) {
-      i.foo(); // <-- will be i.<clinit>()
-    }
-  }
-}