diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/jni_internal.cc | 404 | ||||
| -rw-r--r-- | src/jni_internal.h | 13 | ||||
| -rw-r--r-- | src/jni_internal_test.cc | 2 | ||||
| -rw-r--r-- | src/thread.cc | 4 | ||||
| -rw-r--r-- | src/thread.h | 11 | ||||
| -rw-r--r-- | src/utils.cc | 65 | ||||
| -rw-r--r-- | src/utils.h | 9 | ||||
| -rw-r--r-- | src/utils_test.cc | 29 |
8 files changed, 373 insertions, 164 deletions
diff --git a/src/jni_internal.cc b/src/jni_internal.cc index a95ff81f22..00976c1f67 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 @@ void CreateInvokeStub(Assembler* assembler, Method* method); // 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 @@ bool EnsureInvokeStub(Method* method) { 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 @@ jmethodID FindMethodID(ScopedJniThreadState& ts, jclass jni_class, const char* n 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 @@ jint JII_AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args, bool as 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 @@ class JNI { 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 @@ JavaVMExt::JavaVMExt(Runtime* runtime, bool check_jni, bool verbose_jni) 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 @@ JavaVMExt::~JavaVMExt() { 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 @@ bool JavaVMExt::LoadNativeLibrary(const std::string& path, ClassLoader* class_lo // 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 @@ bool JavaVMExt::LoadNativeLibrary(const std::string& path, ClassLoader* class_lo } // Create a new entry. - library = new SharedLibrary(path, handle, 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); + } + library = new SharedLibrary(path, handle, class_loader); + libraries->Put(path, library); + } - libraries[path] = library; + if (verbose_jni) { + LOG(INFO) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]"; + } - // 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 - { + bool result = true; + void* sym = dlsym(handle, "JNI_OnLoad"); + if (sym == NULL) { if (verbose_jni) { - LOG(INFO) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]"; + LOG(INFO) << "[No JNI_OnLoad found in \"" << path << "\"]"; } - - 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 { - // 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 << "\"]"; - } + LOG(INFO) << "[Returned " << (result ? "successfully" : "failure") + << " from JNI_OnLoad in \"" << path << "\"]"; } } + } - library->SetResult(result); - return result; + 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 2b3529cef2..fec84ea149 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 @@ struct JavaVMExt : public JavaVM { */ 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 @@ struct JavaVMExt : public JavaVM { 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 ddc0408e0a..fc51db032f 100644 --- a/src/jni_internal_test.cc +++ b/src/jni_internal_test.cc @@ -791,7 +791,7 @@ TEST_F(JniInternalTest, DeleteWeakGlobalRef) { 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 0878b50173..ef8501a5bc 100644 --- a/src/thread.cc +++ b/src/thread.cc @@ -304,6 +304,10 @@ void Thread::ThrowNewException(const char* exception_class_descriptor, const cha 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 f695df1f59..820e2aad41 100644 --- a/src/thread.h +++ b/src/thread.h @@ -44,20 +44,20 @@ class Mutex { 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 @@ class Thread { 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 c24504e75f..fd30a362fa 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -105,4 +105,69 @@ std::string PrettyType(const Object* obj) { 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 229d1233b1..e52f476cf9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -152,6 +152,15 @@ std::string PrettyMethod(const Method* m, bool with_signature); // 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 fa5d798a8a..769dec575d 100644 --- a/src/utils_test.cc +++ b/src/utils_test.cc @@ -74,4 +74,33 @@ TEST_F(UtilsTest, PrettyType) { 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 |