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