Handle non-(native|abstract) methods that don't have Code attribute.
Since it's an invalid input to JVM, we can throw a compilation error,
not NPE (due to parts that are expected to be initialied when visiting
a Code attribute).
Bug: 149808321
Change-Id: Ia282dc131768c0bd754a8a1ef8df42f0f3fd3598
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 edec3a9..ec6cba3 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -58,6 +58,7 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
+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.InternalOptions;
@@ -149,7 +150,8 @@
public void parseCode(ReparseContext context, boolean useJsrInliner) {
int parsingOptions = getParsingOptions(application, reachabilitySensitive);
ClassCodeVisitor classVisitor =
- new ClassCodeVisitor(context.owner, createCodeLocator(context), application, useJsrInliner);
+ new ClassCodeVisitor(
+ context.owner, createCodeLocator(context), application, useJsrInliner, origin);
new ClassReader(context.classCache).accept(classVisitor, parsingOptions);
}
@@ -250,17 +252,20 @@
private final BiFunction<String, String, LazyCfCode> codeLocator;
private final JarApplicationReader application;
private boolean usrJsrInliner;
+ private final Origin origin;
ClassCodeVisitor(
DexClass clazz,
BiFunction<String, String, LazyCfCode> codeLocator,
JarApplicationReader application,
- boolean useJsrInliner) {
+ boolean useJsrInliner,
+ Origin origin) {
super(InternalOptions.ASM_VERSION);
this.clazz = clazz;
this.codeLocator = codeLocator;
this.application = application;
this.usrJsrInliner = useJsrInliner;
+ this.origin = origin;
}
@Override
@@ -271,7 +276,8 @@
LazyCfCode code = codeLocator.apply(name, desc);
if (code != null) {
DexMethod method = application.getMethod(clazz.type, name, desc);
- MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, method, code);
+ MethodCodeVisitor methodVisitor =
+ new MethodCodeVisitor(application, method, code, origin);
if (!usrJsrInliner) {
return methodVisitor;
}
@@ -294,14 +300,17 @@
private Map<Label, CfLabel> labelMap;
private final LazyCfCode code;
private final DexMethod method;
+ private final Origin origin;
- MethodCodeVisitor(JarApplicationReader application, DexMethod method, LazyCfCode code) {
+ MethodCodeVisitor(
+ JarApplicationReader application, DexMethod method, LazyCfCode code, Origin origin) {
super(InternalOptions.ASM_VERSION);
assert code != null;
this.application = application;
this.factory = application.getFactory();
this.code = code;
this.method = method;
+ this.origin = origin;
}
@Override
@@ -316,6 +325,15 @@
@Override
public void visitEnd() {
+ if (instructions == null) {
+ // Everything that is initialized at `visitCode` should be null too.
+ assert tryCatchRanges == null && localVariables == null && labelMap == null;
+ // This code visitor is used only if the method is neither abstract nor native, hence it
+ // should have exactly one Code attribute:
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3
+ throw new CompilationError("Absent Code attribute in method that is not native or abstract")
+ .withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method));
+ }
code.setCode(
new CfCode(
method.holder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
diff --git a/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java b/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java
new file mode 100644
index 0000000..c857e00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java
@@ -0,0 +1,104 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class MethodWithoutCodeAttributeTest extends TestBase {
+ private static final String MAIN = "com.android.tools.r8.Test";
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public MethodWithoutCodeAttributeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue("D8 tests.", parameters.isDexRuntime());
+ try {
+ testForD8()
+ .addProgramClassFileData(TestDump.dump())
+ .compile();
+ fail("Expected to fail due to multiple annotations");
+ } catch (CompilationFailedException e) {
+ assertThat(
+ e.getCause().getMessage(),
+ containsString("Absent Code attribute in method that is not native or abstract"));
+ }
+ }
+
+ @Test
+ public void testJVMOutput() throws Exception {
+ assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(TestDump.dump())
+ .run(parameters.getRuntime(), MAIN)
+ .assertFailureWithErrorThatMatches(
+ containsString("Absent Code attribute in method that is not native or abstract"));
+ }
+
+ static class TestDump implements Opcodes {
+ public static byte[] dump () throws Exception {
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8, ACC_SUPER,
+ "com/android/tools/r8/Test",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_SYNTHETIC, "foo", "(Ljava/lang/Object;)V", null, null);
+ methodVisitor.visitAnnotableParameterCount(1, false);
+ // b/149808321: no code attribute
+ methodVisitor.visitEnd();
+ }
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "main", "([Ljava/lang/String;)V", null, null);
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(42, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Test::main");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(44, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}