ART: Fix re-throwing failures of non-convention errors

While it is convention that Throwable subclasses should have a
constructor with a String argument, that is not rigorously enforced.
So if a static initializer throws an error that omits that
constructor, we must not provide a message when trying to throw
again.

Bug: 20495321
Bug: 20497840
Change-Id: Ia4334fa24223750f90a8f2732f1eb1e738575e8d
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index c344eb4..dc8bf2a 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -92,8 +92,28 @@
   va_end(args);
 }
 
-static void ThrowEarlierClassFailure(mirror::Class* c)
+static bool HasInitWithString(Thread* self, ClassLinker* class_linker, const char* descriptor)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  mirror::ArtMethod* method = self->GetCurrentMethod(nullptr);
+  StackHandleScope<1> hs(self);
+  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(method != nullptr ?
+      method->GetDeclaringClass()->GetClassLoader()
+      : nullptr));
+  mirror::Class* exception_class = class_linker->FindClass(self, descriptor, class_loader);
+
+  if (exception_class == nullptr) {
+    // No exc class ~ no <init>-with-string.
+    CHECK(self->IsExceptionPending());
+    self->ClearException();
+    return false;
+  }
+
+  mirror::ArtMethod* exception_init_method =
+      exception_class->FindDeclaredDirectMethod("<init>", "(Ljava/lang/String;)V");
+  return exception_init_method != nullptr;
+}
+
+void ClassLinker::ThrowEarlierClassFailure(mirror::Class* c) {
   // The class failed to initialize on a previous attempt, so we want to throw
   // a NoClassDefFoundError (v2 2.17.5).  The exception to this rule is if we
   // failed in verification, in which case v2 5.4.1 says we need to re-throw
@@ -112,9 +132,15 @@
   } else {
     if (c->GetVerifyErrorClass() != nullptr) {
       // TODO: change the verifier to store an _instance_, with a useful detail message?
+      // It's possible the exception doesn't have a <init>(String).
       std::string temp;
-      self->ThrowNewException(c->GetVerifyErrorClass()->GetDescriptor(&temp),
-                              PrettyDescriptor(c).c_str());
+      const char* descriptor = c->GetVerifyErrorClass()->GetDescriptor(&temp);
+
+      if (HasInitWithString(self, this, descriptor)) {
+        self->ThrowNewException(descriptor, PrettyDescriptor(c).c_str());
+      } else {
+        self->ThrowNewException(descriptor, nullptr);
+      }
     } else {
       self->ThrowNewException("Ljava/lang/NoClassDefFoundError;",
                               PrettyDescriptor(c).c_str());
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 8e27413..1bd9f0a 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -657,6 +657,12 @@
   // Return the quick generic JNI stub for testing.
   const void* GetRuntimeQuickGenericJniStub() const;
 
+  // Throw the class initialization failure recorded when first trying to initialize the given
+  // class.
+  // Note: Currently we only store the descriptor, so we cannot throw the exact throwable, only
+  //       a recreation with a custom string.
+  void ThrowEarlierClassFailure(mirror::Class* c) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
   std::vector<const DexFile*> boot_class_path_;
   std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
 
diff --git a/test/008-exceptions/expected.txt b/test/008-exceptions/expected.txt
index ef6eaff..92c79dc 100644
--- a/test/008-exceptions/expected.txt
+++ b/test/008-exceptions/expected.txt
@@ -1,9 +1,12 @@
 Got an NPE: second throw
 java.lang.NullPointerException: second throw
-	at Main.catchAndRethrow(Main.java:39)
-	at Main.exceptions_007(Main.java:23)
-	at Main.main(Main.java:31)
+	at Main.catchAndRethrow(Main.java:58)
+	at Main.exceptions_007(Main.java:41)
+	at Main.main(Main.java:49)
 Caused by: java.lang.NullPointerException: first throw
-	at Main.throwNullPointerException(Main.java:46)
-	at Main.catchAndRethrow(Main.java:36)
+	at Main.throwNullPointerException(Main.java:65)
+	at Main.catchAndRethrow(Main.java:55)
 	... 2 more
+Static Init
+BadError: This is bad by convention
+BadError: This is bad by convention
diff --git a/test/008-exceptions/src/Main.java b/test/008-exceptions/src/Main.java
index 1f76f12..7f6d0c5 100644
--- a/test/008-exceptions/src/Main.java
+++ b/test/008-exceptions/src/Main.java
@@ -14,6 +14,24 @@
  * limitations under the License.
  */
 
+// An exception that doesn't have a <init>(String) method.
+class BadError extends Error {
+    public BadError() {
+        super("This is bad by convention");
+    }
+}
+
+// A class that throws BadException during static initialization.
+class BadInit {
+    static int dummy;
+    static {
+        System.out.println("Static Init");
+        if (true) {
+            throw new BadError();
+        }
+    }
+}
+
 /**
  * Exceptions across method calls
  */
@@ -29,6 +47,7 @@
     }
     public static void main (String args[]) {
         exceptions_007();
+        exceptionsRethrowClassInitFailure();
     }
 
     private static void catchAndRethrow() {
@@ -45,4 +64,26 @@
     private static void throwNullPointerException() {
         throw new NullPointerException("first throw");
     }
+
+    private static void exceptionsRethrowClassInitFailure() {
+        try {
+            try {
+                BadInit.dummy = 1;
+                throw new IllegalStateException("Should not reach here.");
+            } catch (BadError e) {
+                System.out.println(e);
+            }
+
+            // Check if it works a second time.
+
+            try {
+                BadInit.dummy = 1;
+                throw new IllegalStateException("Should not reach here.");
+            } catch (BadError e) {
+                System.out.println(e);
+            }
+        } catch (Exception error) {
+            error.printStackTrace();
+        }
+    }
 }