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