Reland "Synthesize null-throwing body for abstract methods on non-abstract classes"

This reverts commit f86a2c94f6abebf6819bd6919672ac85eef33a98.

Change-Id: I58ff6b16316e9ed5e6fe49d09e90d026293dae98
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 43bc47d..a1ca852 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -763,7 +763,7 @@
       }
 
       // Validity checks.
-      assert application.classes().stream().allMatch(DexClass::isValid);
+      assert application.classes().stream().allMatch(clazz -> clazz.isValid(options));
       assert appView.rootSet().verifyKeptItemsAreKept(application, appView.appInfo());
       assert appView
           .graphLense()
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 3ef485f..df86e96 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -600,8 +600,12 @@
     return fields;
   }
 
-  private DexEncodedMethod[] readMethods(int size, DexMethodAnnotation[] annotations,
-      DexParameterAnnotation[] parameters, boolean skipCodes) {
+  private DexEncodedMethod[] readMethods(
+      int size,
+      DexMethodAnnotation[] annotations,
+      DexParameterAnnotation[] parameters,
+      boolean skipCodes,
+      boolean ensureNonAbstract) {
     DexEncodedMethod[] methods = new DexEncodedMethod[size];
     int methodIndex = 0;
     MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator =
@@ -618,8 +622,21 @@
         code = codes.get(codeOff);
       }
       DexMethod method = indexedItems.getMethod(methodIndex);
-      methods[i] = new DexEncodedMethod(method, accessFlags, annotationIterator.getNextFor(method),
-          parameterAnnotationsIterator.getNextFor(method), code);
+      DexEncodedMethod encodedMethod =
+          new DexEncodedMethod(
+              method,
+              accessFlags,
+              annotationIterator.getNextFor(method),
+              parameterAnnotationsIterator.getNextFor(method),
+              code);
+      if (accessFlags.isAbstract() && ensureNonAbstract) {
+        accessFlags.unsetAbstract();
+        encodedMethod =
+            options.isGeneratingClassFiles()
+                ? encodedMethod.toEmptyThrowingMethodCf()
+                : encodedMethod.toEmptyThrowingMethodDex();
+      }
+      methods[i] = encodedMethod;
     }
     return methods;
   }
@@ -692,13 +709,17 @@
                 directMethodsSize,
                 annotationsDirectory.methods,
                 annotationsDirectory.parameters,
-                classKind != ClassKind.PROGRAM);
+                classKind != ClassKind.PROGRAM,
+                options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
+                    && !flags.isAbstract());
         virtualMethods =
             readMethods(
                 virtualMethodsSize,
                 annotationsDirectory.methods,
                 annotationsDirectory.parameters,
-                classKind != ClassKind.PROGRAM);
+                classKind != ClassKind.PROGRAM,
+                options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
+                    && !flags.isAbstract());
       }
 
       AttributesAndAnnotations attrs =
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 9a1ea4c..4a2e1e3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -262,6 +262,19 @@
     return true;
   }
 
+  private boolean verifyNoAbstractMethodsOnNonAbstractClasses(
+      Iterable<DexEncodedMethod> methods, InternalOptions options) {
+    if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
+      if (!isAbstract()) {
+        for (DexEncodedMethod method : methods) {
+          assert !method.isAbstract()
+              : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`";
+        }
+      }
+    }
+    return true;
+  }
+
   private boolean verifyNoDuplicateMethods() {
     Set<DexMethod> unique = Sets.newIdentityHashSet();
     for (DexEncodedMethod method : methods()) {
@@ -572,7 +585,10 @@
     return null;
   }
 
-  // Tells whether this is an interface.
+  public boolean isAbstract() {
+    return accessFlags.isAbstract();
+  }
+
   public boolean isInterface() {
     return accessFlags.isInterface();
   }
@@ -876,9 +892,9 @@
     return getKotlinInfo() != null;
   }
 
-  public boolean isValid() {
-    assert !isInterface()
-        || Arrays.stream(virtualMethods).noneMatch(method -> method.accessFlags.isFinal());
+  public boolean isValid(InternalOptions options) {
+    assert verifyNoAbstractMethodsOnNonAbstractClasses(virtualMethods(), options);
+    assert !isInterface() || virtualMethods().stream().noneMatch(DexEncodedMethod::isFinal);
     assert verifyCorrectnessOfFieldHolders(fields());
     assert verifyNoDuplicateFields();
     assert verifyCorrectnessOfMethodHolders(methods());
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index cb90f1f..b44ad18 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -231,6 +231,14 @@
     return compilationState != CompilationState.NOT_PROCESSED;
   }
 
+  public boolean isAbstract() {
+    return accessFlags.isAbstract();
+  }
+
+  public boolean isFinal() {
+    return accessFlags.isFinal();
+  }
+
   public boolean isInitializer() {
     checkIfObsolete();
     return isInstanceInitializer() || isClassInitializer();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 0d42996..a65dd20 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1250,4 +1250,12 @@
   public boolean canHaveDalvikCatchHandlerVerificationBug() {
     return minApiLevel < AndroidApiLevel.L.getLevel();
   }
+
+  // Having an invoke instruction that targets an abstract method on a non-abstract class will fail
+  // with a verification error.
+  //
+  // See b/132953944.
+  public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() {
+    return minApiLevel < AndroidApiLevel.L.getLevel();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 2b32d66..0e60a99 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -118,6 +118,8 @@
   private static Map<String, AndroidApiLevel> needMinSdkVersion =
       new ImmutableMap.Builder<String, AndroidApiLevel>()
           .put("004-JniTest", AndroidApiLevel.N)
+          // Has a non-abstract class with an abstract method.
+          .put("800-smali", AndroidApiLevel.L)
           // Android O
           .put("952-invoke-custom", AndroidApiLevel.O)
           .put("952-invoke-custom-kinds", AndroidApiLevel.O)
@@ -1098,13 +1100,20 @@
           // When running R8 on dex input (D8, DX) this test non-deterministically fails
           // with a compiler exception, due to invoke-virtual on an interface, or it completes but
           // the output when run on Art is not as expected. b/65233869
-          .put("162-method-resolution",
+          .put(
+              "162-method-resolution",
               TestCondition.match(
-                  TestCondition.tools(DexTool.DX, DexTool.NONE),
-                  TestCondition.R8_COMPILER))
+                  TestCondition.tools(DexTool.DX, DexTool.NONE), TestCondition.R8_COMPILER))
           // Produces wrong output when compiled in release mode, which we cannot express.
-          .put("015-switch",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put("015-switch", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          // To prevent "Dex file with version '37' cannot be used with min sdk level '21'", which
+          // would otherwise happen because D8 passes through the DEX code from DX.
+          .put(
+              "800-smali",
+              TestCondition.match(
+                  TestCondition.tools(DexTool.DX),
+                  TestCondition.D8_COMPILER,
+                  TestCondition.runtimesFrom(DexVm.Version.V5_1_1)))
           .build();
 
   public static List<String> requireInliningToBeDisabled =
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
index dc980d7..e9368e9 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -85,13 +85,23 @@
 
   public class ClassBuilder extends Builder {
 
+    private boolean isAbstract = false;
+
     ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
       super(name, superName, implementedInterfaces);
     }
 
+    public ClassBuilder setAbstract() {
+      isAbstract = true;
+      return this;
+    }
+
     public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append(".class public ");
+      if (isAbstract) {
+        builder.append("abstract ");
+      }
       builder.append(DescriptorUtils.javaTypeToDescriptor(name));
       builder.append("\n");
       appendSuper(builder);
@@ -164,10 +174,12 @@
     addClass(name, superName, ImmutableList.of());
   }
 
-  public void addClass(String name, String superName, List<String> implementedInterfaces) {
+  public ClassBuilder addClass(String name, String superName, List<String> implementedInterfaces) {
     assert !classes.containsKey(name);
     currentClassName = name;
-    classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
+    ClassBuilder classBuilder = new ClassBuilder(name, superName, implementedInterfaces);
+    classes.put(name, classBuilder);
+    return classBuilder;
   }
 
   public void addInterface(String name) {
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index 2297ebd..bbeed31 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -380,20 +380,20 @@
   @Test
   public void implementsInterface() {
     SmaliBuilder builder = new SmaliBuilder();
-    builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List"));
+    builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List")).setAbstract();
     builder.addAbstractMethod("int", "test", ImmutableList.of());
     AndroidApp application = buildApplication(builder);
 
     String expected =
-        ".class public LTest;\n" +
-            "\n" +
-            ".super Ljava/lang/Object;\n" +
-            ".implements Ljava/util/List;\n" +
-            "\n" +
-            ".method public abstract test()I\n" +
-            ".end method\n" +
-            "\n" +
-            "# End of class LTest;\n";
+        ".class public abstract LTest;\n"
+            + "\n"
+            + ".super Ljava/lang/Object;\n"
+            + ".implements Ljava/util/List;\n"
+            + "\n"
+            + ".method public abstract test()I\n"
+            + ".end method\n"
+            + "\n"
+            + "# End of class LTest;\n";
 
     assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));