Add the runtime lookup of native method implementations.

Plus other bits of cleanup.

Change-Id: I8584001d7eeb118f8e29c4a62652a18b94333be8
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index a95ff81..00976c1 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -2,9 +2,11 @@
 
 #include "jni_internal.h"
 
-#include <cstdarg>
 #include <dlfcn.h>
 #include <sys/mman.h>
+
+#include <cstdarg>
+#include <map>
 #include <utility>
 #include <vector>
 
@@ -28,9 +30,9 @@
 
 // TODO: this should be in our anonymous namespace, but is currently needed
 // for testing in "jni_internal_test.cc".
-bool EnsureInvokeStub(Method* method) {
+void EnsureInvokeStub(Method* method) {
   if (method->GetInvokeStub() != NULL) {
-    return true;
+    return;
   }
   // TODO: use signature to find a matching stub
   // TODO: failed, acquire a lock on the stub table
@@ -46,98 +48,8 @@
   MemoryRegion region(addr, length);
   assembler.FinalizeInstructions(region);
   method->SetInvokeStub(reinterpret_cast<Method::InvokeStub*>(region.pointer()));
-  return true;
 }
 
-// TODO: this can't be in our anonymous namespace because of the map in JavaVM.
-class SharedLibrary {
-public:
-  SharedLibrary(const std::string& path, void* handle, Object* class_loader)
-      : path_(path),
-        handle_(handle),
-        jni_on_load_lock_(Mutex::Create("JNI_OnLoad lock")),
-        jni_on_load_tid_(Thread::Current()->GetId()),
-        jni_on_load_result_(kPending) {
-    pthread_cond_init(&jni_on_load_cond_, NULL);
-  }
-
-  ~SharedLibrary() {
-    delete jni_on_load_lock_;
-  }
-
-  Object* GetClassLoader() {
-    return class_loader_;
-  }
-
-  /*
-   * Check the result of an earlier call to JNI_OnLoad on this library.  If
-   * the call has not yet finished in another thread, wait for it.
-   */
-  bool CheckOnLoadResult(JavaVMExt* vm) {
-    Thread* self = Thread::Current();
-    if (jni_on_load_tid_ == self->GetId()) {
-      // Check this so we don't end up waiting for ourselves.  We need
-      // to return "true" so the caller can continue.
-      LOG(INFO) << *self << " recursive attempt to load library "
-                << "\"" << path_ << "\"";
-      return true;
-    }
-
-    MutexLock mu(jni_on_load_lock_);
-    while (jni_on_load_result_ == kPending) {
-      if (vm->verbose_jni) {
-        LOG(INFO) << "[" << *self << " waiting for \"" << path_ << "\" "
-                  << "JNI_OnLoad...]";
-      }
-      Thread::State old_state = self->GetState();
-      self->SetState(Thread::kWaiting); // TODO: VMWAIT
-      pthread_cond_wait(&jni_on_load_cond_, &(jni_on_load_lock_->lock_impl_));
-      self->SetState(old_state);
-    }
-
-    bool okay = (jni_on_load_result_ == kOkay);
-    if (vm->verbose_jni) {
-      LOG(INFO) << "[Earlier JNI_OnLoad for \"" << path_ << "\" "
-                << (okay ? "succeeded" : "failed") << "]";
-    }
-    return okay;
-  }
-
-  void SetResult(bool result) {
-    jni_on_load_result_ = result ? kOkay : kFailed;
-    jni_on_load_tid_ = 0;
-
-    // Broadcast a wakeup to anybody sleeping on the condition variable.
-    MutexLock mu(jni_on_load_lock_);
-    pthread_cond_broadcast(&jni_on_load_cond_);
-  }
-
- private:
-  enum JNI_OnLoadState {
-    kPending,
-    kFailed,
-    kOkay,
-  };
-
-  // Path to library "/system/lib/libjni.so".
-  std::string path_;
-
-  // The void* returned by dlopen(3).
-  void* handle_;
-
-  // The ClassLoader this library is associated with.
-  Object* class_loader_;
-
-  // Guards remaining items.
-  Mutex* jni_on_load_lock_;
-  // Wait for JNI_OnLoad in other thread.
-  pthread_cond_t jni_on_load_cond_;
-  // Recursive invocation guard.
-  uint32_t jni_on_load_tid_;
-  // Result of earlier JNI_OnLoad call.
-  JNI_OnLoadState jni_on_load_result_;
-};
-
 namespace {
 
 // Entry/exit processing for all JNI calls.
@@ -459,11 +371,7 @@
     return NULL;
   }
 
-  bool success = EnsureInvokeStub(method);
-  if (!success) {
-    // TODO: throw OutOfMemoryException
-    return NULL;
-  }
+  EnsureInvokeStub(method);
 
   return reinterpret_cast<jmethodID>(AddWeakGlobalReference(ts, method));
 }
@@ -603,8 +511,164 @@
   return runtime->AttachCurrentThread(args.name, p_env, as_daemon) ? JNI_OK : JNI_ERR;
 }
 
+class SharedLibrary {
+ public:
+  SharedLibrary(const std::string& path, void* handle, Object* class_loader)
+      : path_(path),
+        handle_(handle),
+        jni_on_load_lock_(Mutex::Create("JNI_OnLoad lock")),
+        jni_on_load_tid_(Thread::Current()->GetId()),
+        jni_on_load_result_(kPending) {
+    pthread_cond_init(&jni_on_load_cond_, NULL);
+  }
+
+  ~SharedLibrary() {
+    delete jni_on_load_lock_;
+  }
+
+  Object* GetClassLoader() {
+    return class_loader_;
+  }
+
+  std::string GetPath() {
+    return path_;
+  }
+
+  /*
+   * Check the result of an earlier call to JNI_OnLoad on this library.  If
+   * the call has not yet finished in another thread, wait for it.
+   */
+  bool CheckOnLoadResult(JavaVMExt* vm) {
+    Thread* self = Thread::Current();
+    if (jni_on_load_tid_ == self->GetId()) {
+      // Check this so we don't end up waiting for ourselves.  We need
+      // to return "true" so the caller can continue.
+      LOG(INFO) << *self << " recursive attempt to load library "
+                << "\"" << path_ << "\"";
+      return true;
+    }
+
+    MutexLock mu(jni_on_load_lock_);
+    while (jni_on_load_result_ == kPending) {
+      if (vm->verbose_jni) {
+        LOG(INFO) << "[" << *self << " waiting for \"" << path_ << "\" "
+                  << "JNI_OnLoad...]";
+      }
+      Thread::State old_state = self->GetState();
+      self->SetState(Thread::kWaiting); // TODO: VMWAIT
+      pthread_cond_wait(&jni_on_load_cond_, jni_on_load_lock_->GetImpl());
+      self->SetState(old_state);
+    }
+
+    bool okay = (jni_on_load_result_ == kOkay);
+    if (vm->verbose_jni) {
+      LOG(INFO) << "[Earlier JNI_OnLoad for \"" << path_ << "\" "
+                << (okay ? "succeeded" : "failed") << "]";
+    }
+    return okay;
+  }
+
+  void SetResult(bool result) {
+    jni_on_load_result_ = result ? kOkay : kFailed;
+    jni_on_load_tid_ = 0;
+
+    // Broadcast a wakeup to anybody sleeping on the condition variable.
+    MutexLock mu(jni_on_load_lock_);
+    pthread_cond_broadcast(&jni_on_load_cond_);
+  }
+
+  void* FindSymbol(const std::string& symbol_name) {
+    return dlsym(handle_, symbol_name.c_str());
+  }
+
+ private:
+  enum JNI_OnLoadState {
+    kPending,
+    kFailed,
+    kOkay,
+  };
+
+  // Path to library "/system/lib/libjni.so".
+  std::string path_;
+
+  // The void* returned by dlopen(3).
+  void* handle_;
+
+  // The ClassLoader this library is associated with.
+  Object* class_loader_;
+
+  // Guards remaining items.
+  Mutex* jni_on_load_lock_;
+  // Wait for JNI_OnLoad in other thread.
+  pthread_cond_t jni_on_load_cond_;
+  // Recursive invocation guard.
+  uint32_t jni_on_load_tid_;
+  // Result of earlier JNI_OnLoad call.
+  JNI_OnLoadState jni_on_load_result_;
+};
+
 }  // namespace
 
+// This exists mainly to keep implementation details out of the header file.
+class Libraries {
+ public:
+  Libraries() {
+  }
+
+  ~Libraries() {
+    // Delete our map values. (The keys will be cleaned up by the map itself.)
+    for (It it = libraries_.begin(); it != libraries_.end(); ++it) {
+      delete it->second;
+    }
+  }
+
+  SharedLibrary* Get(const std::string& path) {
+    return libraries_[path];
+  }
+
+  void Put(const std::string& path, SharedLibrary* library) {
+    libraries_[path] = library;
+  }
+
+  // See section 11.3 "Linking Native Methods" of the JNI spec.
+  void* FindNativeMethod(const Method* m) {
+    std::string jni_short_name(JniShortName(m));
+    std::string jni_long_name(JniLongName(m));
+    ClassLoader* declaring_class_loader = m->GetDeclaringClass()->GetClassLoader();
+    for (It it = libraries_.begin(); it != libraries_.end(); ++it) {
+      SharedLibrary* library = it->second;
+      if (library->GetClassLoader() != declaring_class_loader) {
+        // We only search libraries loaded by the appropriate ClassLoader.
+        continue;
+      }
+      // Try the short name then the long name...
+      void* fn = library->FindSymbol(jni_short_name);
+      if (fn == NULL) {
+        fn = library->FindSymbol(jni_long_name);
+      }
+      if (fn != NULL) {
+        if (Runtime::Current()->GetJavaVM()->verbose_jni) {
+          LOG(INFO) << "[Found native code for " << PrettyMethod(m, true)
+                    << " in \"" << library->GetPath() << "\"]";
+        }
+        return fn;
+      }
+    }
+    std::string detail;
+    detail += "No implementation found for ";
+    detail += PrettyMethod(m, true);
+    LOG(ERROR) << detail;
+    Thread::Current()->ThrowNewException("Ljava/lang/UnsatisfiedLinkError;",
+        "%s", detail.c_str());
+    return NULL;
+  }
+
+ private:
+  typedef std::map<std::string, SharedLibrary*>::iterator It; // TODO: C++0x auto
+
+  std::map<std::string, SharedLibrary*> libraries_;
+};
+
 class JNI {
  public:
 
@@ -1837,7 +1901,7 @@
     size_t byte_count = s->GetUtfLength();
     char* bytes = new char[byte_count + 1];
     if (bytes == NULL) {
-      UNIMPLEMENTED(WARNING) << "need to Throw OOME";
+      ts.Self()->ThrowOutOfMemoryError();
       return NULL;
     }
     const uint16_t* chars = s->GetCharArray()->GetData() + s->GetOffset();
@@ -2641,7 +2705,9 @@
       globals_lock(Mutex::Create("JNI global reference table lock")),
       globals(kGlobalsInitial, kGlobalsMax, kGlobal),
       weak_globals_lock(Mutex::Create("JNI weak global reference table lock")),
-      weak_globals(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal) {
+      weak_globals(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal),
+      libraries_lock(Mutex::Create("JNI shared libraries map lock")),
+      libraries(new Libraries) {
   functions = &gInvokeInterface;
 }
 
@@ -2649,10 +2715,10 @@
   delete pins_lock;
   delete globals_lock;
   delete weak_globals_lock;
+  delete libraries_lock;
+  delete libraries;
 }
 
-/*
- */
 bool JavaVMExt::LoadNativeLibrary(const std::string& path, ClassLoader* class_loader, std::string& detail) {
   detail.clear();
 
@@ -2660,7 +2726,12 @@
   // matches, return successfully without doing anything.
   // TODO: for better results we should canonicalize the pathname (or even compare
   // inodes). This implementation is fine if everybody is using System.loadLibrary.
-  SharedLibrary* library = libraries[path];
+  SharedLibrary* library;
+  {
+    // TODO: move the locking (and more of this logic) into Libraries.
+    MutexLock mu(libraries_lock);
+    library = libraries->Get(path);
+  }
   if (library != NULL) {
     if (library->GetClassLoader() != class_loader) {
       // The library will be associated with class_loader. The JNI
@@ -2727,69 +2798,90 @@
   }
 
   // Create a new entry.
-  library = new SharedLibrary(path, handle, class_loader);
-
-  libraries[path] = library;
-
-  //  if (pNewEntry != pActualEntry) {
-  //    LOG(INFO) << "WOW: we lost a race to add a shared library (\"" << path << "\" ClassLoader=" << class_loader <<")";
-  //    freeSharedLibEntry(pNewEntry);
-  //    return CheckOnLoadResult(this, pActualEntry);
-  //  } else
   {
-    if (verbose_jni) {
-      LOG(INFO) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
+    // TODO: move the locking (and more of this logic) into Libraries.
+    MutexLock mu(libraries_lock);
+    library = libraries->Get(path);
+    if (library != NULL) {
+      LOG(INFO) << "WOW: we lost a race to add shared library: "
+                << "\"" << path << "\" ClassLoader=" << class_loader;
+      return library->CheckOnLoadResult(this);
     }
-
-    bool result = true;
-    void* sym = dlsym(handle, "JNI_OnLoad");
-    if (sym == NULL) {
-      if (verbose_jni) {
-        LOG(INFO) << "[No JNI_OnLoad found in \"" << path << "\"]";
-      }
-    } else {
-      // Call JNI_OnLoad.  We have to override the current class
-      // loader, which will always be "null" since the stuff at the
-      // top of the stack is around Runtime.loadLibrary().  (See
-      // the comments in the JNI FindClass function.)
-      typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
-      JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
-      ClassLoader* old_class_loader = self->GetClassLoaderOverride();
-      self->SetClassLoaderOverride(class_loader);
-
-      old_state = self->GetState();
-      self->SetState(Thread::kNative);
-      if (verbose_jni) {
-        LOG(INFO) << "[Calling JNI_OnLoad in \"" << path << "\"]";
-      }
-      int version = (*jni_on_load)(this, NULL);
-      self->SetState(old_state);
-
-      self->SetClassLoaderOverride(old_class_loader);;
-
-      if (version != JNI_VERSION_1_2 &&
-      version != JNI_VERSION_1_4 &&
-      version != JNI_VERSION_1_6) {
-        LOG(WARNING) << "JNI_OnLoad in \"" << path << "\" returned "
-                     << "bad version: " << version;
-        // It's unwise to call dlclose() here, but we can mark it
-        // as bad and ensure that future load attempts will fail.
-        // We don't know how far JNI_OnLoad got, so there could
-        // be some partially-initialized stuff accessible through
-        // newly-registered native method calls.  We could try to
-        // unregister them, but that doesn't seem worthwhile.
-        result = false;
-      } else {
-        if (verbose_jni) {
-          LOG(INFO) << "[Returned " << (result ? "successfully" : "failure")
-                    << " from JNI_OnLoad in \"" << path << "\"]";
-        }
-      }
-    }
-
-    library->SetResult(result);
-    return result;
+    library = new SharedLibrary(path, handle, class_loader);
+    libraries->Put(path, library);
   }
+
+  if (verbose_jni) {
+    LOG(INFO) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
+  }
+
+  bool result = true;
+  void* sym = dlsym(handle, "JNI_OnLoad");
+  if (sym == NULL) {
+    if (verbose_jni) {
+      LOG(INFO) << "[No JNI_OnLoad found in \"" << path << "\"]";
+    }
+  } else {
+    // Call JNI_OnLoad.  We have to override the current class
+    // loader, which will always be "null" since the stuff at the
+    // top of the stack is around Runtime.loadLibrary().  (See
+    // the comments in the JNI FindClass function.)
+    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
+    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
+    ClassLoader* old_class_loader = self->GetClassLoaderOverride();
+    self->SetClassLoaderOverride(class_loader);
+
+    old_state = self->GetState();
+    self->SetState(Thread::kNative);
+    if (verbose_jni) {
+      LOG(INFO) << "[Calling JNI_OnLoad in \"" << path << "\"]";
+    }
+    int version = (*jni_on_load)(this, NULL);
+    self->SetState(old_state);
+
+    self->SetClassLoaderOverride(old_class_loader);;
+
+    if (version != JNI_VERSION_1_2 &&
+    version != JNI_VERSION_1_4 &&
+    version != JNI_VERSION_1_6) {
+      LOG(WARNING) << "JNI_OnLoad in \"" << path << "\" returned "
+                   << "bad version: " << version;
+      // It's unwise to call dlclose() here, but we can mark it
+      // as bad and ensure that future load attempts will fail.
+      // We don't know how far JNI_OnLoad got, so there could
+      // be some partially-initialized stuff accessible through
+      // newly-registered native method calls.  We could try to
+      // unregister them, but that doesn't seem worthwhile.
+      result = false;
+    } else {
+      if (verbose_jni) {
+        LOG(INFO) << "[Returned " << (result ? "successfully" : "failure")
+                  << " from JNI_OnLoad in \"" << path << "\"]";
+      }
+    }
+  }
+
+  library->SetResult(result);
+  return result;
+}
+
+void* JavaVMExt::FindCodeForNativeMethod(Method* m) {
+  CHECK(m->IsNative());
+
+  Class* c = m->GetDeclaringClass();
+
+  // If this is a static method, it could be called before the class
+  // has been initialized.
+  if (m->IsStatic()) {
+    if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(c)) {
+      return NULL;
+    }
+  } else {
+    CHECK_GE(c->GetStatus(), Class::kStatusInitializing);
+  }
+
+  MutexLock mu(libraries_lock);
+  return libraries->FindNativeMethod(m);
 }
 
 }  // namespace art
diff --git a/src/jni_internal.h b/src/jni_internal.h
index 2b3529c..fec84ea 100644
--- a/src/jni_internal.h
+++ b/src/jni_internal.h
@@ -9,15 +9,15 @@
 #include "macros.h"
 #include "reference_table.h"
 
-#include <map>
 #include <string>
 
 namespace art {
 
 class ClassLoader;
+class Libraries;
+class Method;
 class Mutex;
 class Runtime;
-class SharedLibrary;
 class Thread;
 
 struct JavaVMExt : public JavaVM {
@@ -32,6 +32,12 @@
    */
   bool LoadNativeLibrary(const std::string& path, ClassLoader* class_loader, std::string& detail);
 
+  /**
+   * Returns a pointer to the code for the native method 'm', found
+   * using dlsym(3) on every native library that's been loaded so far.
+   */
+  void* FindCodeForNativeMethod(Method* m);
+
   Runtime* runtime;
 
   bool check_jni;
@@ -49,7 +55,8 @@
   Mutex* weak_globals_lock;
   IndirectReferenceTable weak_globals;
 
-  std::map<std::string, SharedLibrary*> libraries;
+  Mutex* libraries_lock;
+  Libraries* libraries;
 };
 
 struct JNIEnvExt : public JNIEnv {
diff --git a/src/jni_internal_test.cc b/src/jni_internal_test.cc
index ddc0408..fc51db0 100644
--- a/src/jni_internal_test.cc
+++ b/src/jni_internal_test.cc
@@ -791,7 +791,7 @@
   env_->DeleteWeakGlobalRef(o2);
 }
 
-bool EnsureInvokeStub(Method* method);
+void EnsureInvokeStub(Method* method);
 
 Method::InvokeStub* AllocateStub(Method* method,
                                  byte* code,
diff --git a/src/thread.cc b/src/thread.cc
index 0878b50..ef8501a 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -304,6 +304,10 @@
   CHECK_EQ(rc, JNI_OK);
 }
 
+void Thread::ThrowOutOfMemoryError() {
+  UNIMPLEMENTED(FATAL);
+}
+
 Frame Thread::FindExceptionHandler(void* throw_pc, void** handler_pc) {
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   DCHECK(class_linker != NULL);
diff --git a/src/thread.h b/src/thread.h
index f695df1..820e2aa 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -44,20 +44,20 @@
 
   static Mutex* Create(const char* name);
 
- public:  // TODO: protected
-  void SetOwner(Thread* thread) { owner_ = thread; }
+  // TODO: only needed because we lack a condition variable abstraction.
+  pthread_mutex_t* GetImpl() { return &lock_impl_; }
 
  private:
   explicit Mutex(const char* name) : name_(name), owner_(NULL) {}
 
+  void SetOwner(Thread* thread) { owner_ = thread; }
+
   const char* name_;
 
   Thread* owner_;
 
   pthread_mutex_t lock_impl_;
 
-  friend class SharedLibrary; // For lock_impl_.
-
   DISALLOW_COPY_AND_ASSIGN(Mutex);
 };
 
@@ -256,6 +256,9 @@
   void ThrowNewException(const char* exception_class_descriptor, const char* fmt, ...)
       __attribute__ ((format(printf, 3, 4)));
 
+  // This exception is special, because we need to pre-allocate an instance.
+  void ThrowOutOfMemoryError();
+
   void ClearException() {
     exception_ = NULL;
   }
diff --git a/src/utils.cc b/src/utils.cc
index c24504e..fd30a36 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -105,4 +105,69 @@
   return result;
 }
 
+std::string MangleForJni(const std::string& s) {
+  std::string result;
+  size_t char_count = CountModifiedUtf8Chars(s.c_str());
+  const char* cp = &s[0];
+  for (size_t i = 0; i < char_count; ++i) {
+    uint16_t ch = GetUtf16FromUtf8(&cp);
+    if (ch == '$' || ch > 127) {
+      StringAppendF(&result, "_0%04x", ch);
+    } else {
+      switch (ch) {
+      case '_':
+        result += "_1";
+        break;
+      case ';':
+        result += "_2";
+        break;
+      case '[':
+        result += "_3";
+        break;
+      case '/':
+        result += "_";
+        break;
+      default:
+        result.push_back(ch);
+        break;
+      }
+    }
+  }
+  return result;
+}
+
+std::string JniShortName(const Method* m) {
+  Class* declaring_class = m->GetDeclaringClass();
+
+  std::string class_name(declaring_class->GetDescriptor()->ToModifiedUtf8());
+  // Remove the leading 'L' and trailing ';'...
+  CHECK(class_name[0] == 'L') << class_name;
+  CHECK(class_name[class_name.size() - 1] == ';') << class_name;
+  class_name.erase(0, 1);
+  class_name.erase(class_name.size() - 1, 1);
+
+  std::string method_name(m->GetName()->ToModifiedUtf8());
+
+  std::string short_name;
+  short_name += "Java_";
+  short_name += MangleForJni(class_name);
+  short_name += "_";
+  short_name += MangleForJni(method_name);
+  return short_name;
+}
+
+std::string JniLongName(const Method* m) {
+  std::string long_name;
+  long_name += JniShortName(m);
+  long_name += "__";
+
+  std::string signature(m->GetSignature()->ToModifiedUtf8());
+  signature.erase(0, 1);
+  signature.erase(signature.begin() + signature.find(')'), signature.end());
+
+  long_name += MangleForJni(signature);
+
+  return long_name;
+}
+
 }  // namespace art
diff --git a/src/utils.h b/src/utils.h
index 229d123..e52f476 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -152,6 +152,15 @@
 // Given String.class, the output would be "java.lang.Class<java.lang.String>".
 std::string PrettyType(const Object* obj);
 
+// Performs JNI name mangling as described in section 11.3 "Linking Native Methods"
+// of the JNI spec.
+std::string MangleForJni(const std::string& s);
+
+// Returns the JNI native function name for the non-overloaded method 'm'.
+std::string JniShortName(const Method* m);
+// Returns the JNI native function name for the overloaded method 'm'.
+std::string JniLongName(const Method* m);
+
 std::string ReadFileToString(const char* file_name);
 
 }  // namespace art
diff --git a/src/utils_test.cc b/src/utils_test.cc
index fa5d798..769dec5 100644
--- a/src/utils_test.cc
+++ b/src/utils_test.cc
@@ -74,4 +74,33 @@
   EXPECT_EQ("java.lang.Class<java.lang.String[]>", PrettyType(o->GetClass()));
 }
 
+TEST_F(UtilsTest, MangleForJni) {
+  EXPECT_EQ("hello_00024world", MangleForJni("hello$world"));
+  EXPECT_EQ("hello_000a9world", MangleForJni("hello\xc2\xa9world"));
+  EXPECT_EQ("hello_1world", MangleForJni("hello_world"));
+  EXPECT_EQ("Ljava_lang_String_2", MangleForJni("Ljava/lang/String;"));
+  EXPECT_EQ("_3C", MangleForJni("[C"));
+}
+
+TEST_F(UtilsTest, JniShortName_JniLongName) {
+  Class* c = class_linker_->FindSystemClass("Ljava/lang/String;");
+  ASSERT_TRUE(c != NULL);
+  Method* m;
+
+  m = c->FindVirtualMethod("charAt", "(I)C");
+  ASSERT_TRUE(m != NULL);
+  EXPECT_EQ("Java_java_lang_String_charAt", JniShortName(m));
+  EXPECT_EQ("Java_java_lang_String_charAt__I", JniLongName(m));
+
+  m = c->FindVirtualMethod("indexOf", "(Ljava/lang/String;I)I");
+  ASSERT_TRUE(m != NULL);
+  EXPECT_EQ("Java_java_lang_String_indexOf", JniShortName(m));
+  EXPECT_EQ("Java_java_lang_String_indexOf__Ljava_lang_String_2I", JniLongName(m));
+
+  m = c->FindDirectMethod("copyValueOf", "([CII)Ljava/lang/String;");
+  ASSERT_TRUE(m != NULL);
+  EXPECT_EQ("Java_java_lang_String_copyValueOf", JniShortName(m));
+  EXPECT_EQ("Java_java_lang_String_copyValueOf___3CII", JniLongName(m));
+}
+
 }  // namespace art