ART: Improve double-JNI-load exception message

Try to print the involved classloaders. The code is suboptimal
and will not cache any intermediates, as this is an unexpected
failure - but aids in debugging application issues.

Add a test for the behavior of duplicate library loading in
separate classloaders. For ART, add a rough test for the pattern
of the error message.

Bug: 65574359
Test: m test-art-host
Test: art/test/testrunner/testrunner.py -b --host -t 004-JniTest
Change-Id: I6c6c7726a79172f39153d4a458eba4fb3e8e85b0
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc
index 1593577..c0d1861 100644
--- a/runtime/java_vm_ext.cc
+++ b/runtime/java_vm_ext.cc
@@ -35,6 +35,7 @@
 #include "mirror/class_loader.h"
 #include "nativebridge/native_bridge.h"
 #include "nativehelper/ScopedLocalRef.h"
+#include "nativehelper/ScopedUtfChars.h"
 #include "nativeloader/native_loader.h"
 #include "object_callbacks.h"
 #include "parsed_options.h"
@@ -833,9 +834,42 @@
       // The library will be associated with class_loader. The JNI
       // spec says we can't load the same library into more than one
       // class loader.
+      //
+      // This isn't very common. So spend some time to get a readable message.
+      auto call_to_string = [&](jobject obj) -> std::string {
+        if (obj == nullptr) {
+          return "null";
+        }
+        // Handle jweaks. Ignore double local-ref.
+        ScopedLocalRef<jobject> local_ref(env, env->NewLocalRef(obj));
+        if (local_ref != nullptr) {
+          ScopedLocalRef<jclass> local_class(env, env->GetObjectClass(local_ref.get()));
+          jmethodID to_string = env->GetMethodID(local_class.get(),
+                                                 "toString",
+                                                 "()Ljava/lang/String;");
+          DCHECK(to_string != nullptr);
+          ScopedLocalRef<jobject> local_string(env,
+                                               env->CallObjectMethod(local_ref.get(), to_string));
+          if (local_string != nullptr) {
+            ScopedUtfChars utf(env, reinterpret_cast<jstring>(local_string.get()));
+            if (utf.c_str() != nullptr) {
+              return utf.c_str();
+            }
+          }
+          env->ExceptionClear();
+          return "(Error calling toString)";
+        }
+        return "null";
+      };
+      std::string old_class_loader = call_to_string(library->GetClassLoader());
+      std::string new_class_loader = call_to_string(class_loader);
       StringAppendF(error_msg, "Shared library \"%s\" already opened by "
-          "ClassLoader %p; can't open in ClassLoader %p",
-          path.c_str(), library->GetClassLoader(), class_loader);
+          "ClassLoader %p(%s); can't open in ClassLoader %p(%s)",
+          path.c_str(),
+          library->GetClassLoader(),
+          old_class_loader.c_str(),
+          class_loader,
+          new_class_loader.c_str());
       LOG(WARNING) << *error_msg;
       return false;
     }