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>()
- }
- }
-}