Merge "Get rid of some C artifacts as noted in the initial review." into dalvik-dev
diff --git a/src/assembler_arm.cc b/src/assembler_arm.cc
index c13db39..2022ee9 100644
--- a/src/assembler_arm.cc
+++ b/src/assembler_arm.cc
@@ -1499,6 +1499,13 @@
                  base.AsCoreRegister(), offs.Int32Value());
 }
 
+void Assembler::LoadRawPtr(ManagedRegister dest, ManagedRegister base,
+                           Offset offs) {
+  CHECK(dest.IsCoreRegister() && dest.IsCoreRegister());
+  LoadFromOffset(kLoadWord, dest.AsCoreRegister(),
+                 base.AsCoreRegister(), offs.Int32Value());
+}
+
 void Assembler::StoreImmediateToFrame(FrameOffset dest, uint32_t imm,
                                       ManagedRegister scratch) {
   CHECK(scratch.IsCoreRegister());
diff --git a/src/assembler_arm.h b/src/assembler_arm.h
index 99ff9fe..bad3023 100644
--- a/src/assembler_arm.h
+++ b/src/assembler_arm.h
@@ -431,6 +431,7 @@
 
   void CopyRef(FrameOffset dest, FrameOffset src, ManagedRegister scratch);
   void LoadRef(ManagedRegister dest, ManagedRegister base, MemberOffset offs);
+  void LoadRawPtr(ManagedRegister dest, ManagedRegister base, Offset offs);
 
   void StoreImmediateToFrame(FrameOffset dest, uint32_t imm,
                              ManagedRegister scratch);
diff --git a/src/assembler_x86.cc b/src/assembler_x86.cc
index b6183a9..693f1b4 100644
--- a/src/assembler_x86.cc
+++ b/src/assembler_x86.cc
@@ -1487,6 +1487,12 @@
   movl(dest.AsCpuRegister(), Address(base.AsCpuRegister(), offs));
 }
 
+void Assembler::LoadRawPtr(ManagedRegister dest, ManagedRegister base,
+                           Offset offs) {
+  CHECK(dest.IsCpuRegister() && dest.IsCpuRegister());
+  movl(dest.AsCpuRegister(), Address(base.AsCpuRegister(), offs));
+}
+
 void Assembler::LoadRawPtrFromThread(ManagedRegister dest, ThreadOffset offs) {
   CHECK(dest.IsCpuRegister());
   fs()->movl(dest.AsCpuRegister(), Address::Absolute(offs));
diff --git a/src/assembler_x86.h b/src/assembler_x86.h
index 2c7f2d2..afccc4d 100644
--- a/src/assembler_x86.h
+++ b/src/assembler_x86.h
@@ -125,6 +125,10 @@
     Init(base, disp);
   }
 
+  Address(Register base, Offset disp) {
+    Init(base, disp.Int32Value());
+  }
+
   Address(Register base, FrameOffset disp) {
     CHECK_EQ(base, ESP);
     Init(ESP, disp.Int32Value());
@@ -442,6 +446,8 @@
 
   void LoadRef(ManagedRegister dest, ManagedRegister base, MemberOffset offs);
 
+  void LoadRawPtr(ManagedRegister dest, ManagedRegister base, Offset offs);
+
   void LoadRawPtrFromThread(ManagedRegister dest, ThreadOffset offs);
 
   void CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset thr_offs,
diff --git a/src/jni_compiler.cc b/src/jni_compiler.cc
index dd7d5e5..f3d13c8 100644
--- a/src/jni_compiler.cc
+++ b/src/jni_compiler.cc
@@ -25,6 +25,9 @@
   JniCallingConvention jni_conv(native_method);
   ManagedRuntimeCallingConvention mr_conv(native_method);
   const bool is_static = native_method->IsStatic();
+  static Offset functions(OFFSETOF_MEMBER(JNIEnvExt, fns));
+  static Offset monitor_enter(OFFSETOF_MEMBER(JNINativeInterface, MonitorEnter));
+  static Offset monitor_exit(OFFSETOF_MEMBER(JNINativeInterface, MonitorExit));
 
   // 1. Build the frame
   const size_t frame_size(jni_conv.FrameSize());
@@ -136,9 +139,10 @@
       FrameOffset out_off = jni_conv.CurrentParamStackOffset();
       jni_asm->StoreRawPtr(out_off, jni_env_register);
     }
-    // Call JNIEnvExt::MonitorEnterHelper(JNIEnv*, object)
-    static Offset monitor_enter(OFFSETOF_MEMBER(JNIEnvExt, MonitorEnterHelper));
-    jni_asm->Call(jni_env_register, monitor_enter,
+    // Call JNIEnv->MonitorEnter(object)
+    ManagedRegister jni_fns_register = jni_conv.InterproceduralScratchRegister();
+    jni_asm->LoadRawPtr(jni_fns_register, jni_env_register, functions);
+    jni_asm->Call(jni_fns_register, monitor_enter,
                   jni_conv.InterproceduralScratchRegister());
     jni_asm->FillFromSpillArea(spill_regs, out_arg_size);
     jni_asm->ExceptionPoll(jni_conv.InterproceduralScratchRegister());
@@ -238,9 +242,10 @@
       FrameOffset out_off = jni_conv.CurrentParamStackOffset();
       jni_asm->StoreRawPtr(out_off, jni_env_register);
     }
-    // Call JNIEnvExt::MonitorExitHelper(JNIEnv*, object)
-    static Offset monitor_exit(OFFSETOF_MEMBER(JNIEnvExt, MonitorExitHelper));
-    jni_asm->Call(jni_env_register, monitor_exit,
+    // Call JNIEnv->MonitorExit(object)
+    ManagedRegister jni_fns_register = jni_conv.InterproceduralScratchRegister();
+    jni_asm->LoadRawPtr(jni_fns_register, jni_env_register, functions);
+    jni_asm->Call(jni_fns_register, monitor_exit,
                   jni_conv.InterproceduralScratchRegister());
     // Reload return value
     jni_asm->Load(jni_conv.ReturnRegister(), return_save_location,
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index eda3e9b..d6cfcb9 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -89,7 +89,7 @@
   size_t num_bytes = method->NumArgArrayBytes();
   scoped_array<byte> arg_array(new byte[num_bytes]);
   const StringPiece& shorty = method->GetShorty();
-  for (int i = 1, offset = 0; i < shorty.size() - 1; ++i) {
+  for (int i = 1, offset = 0; i < shorty.size(); ++i) {
     switch (shorty[i]) {
       case 'Z':
       case 'B':
@@ -127,7 +127,7 @@
   size_t num_bytes = method->NumArgArrayBytes();
   scoped_array<byte> arg_array(new byte[num_bytes]);
   const StringPiece& shorty = method->GetShorty();
-  for (int i = 1, offset = 0; i < shorty.size() - 1; ++i) {
+  for (int i = 1, offset = 0; i < shorty.size(); ++i) {
     switch (shorty[i]) {
       case 'Z':
       case 'B':
@@ -284,8 +284,23 @@
 
 jthrowable ExceptionOccurred(JNIEnv* env) {
   ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
+  Object* exception = ts.Self()->GetException();
+  if (exception == NULL) {
+    return NULL;
+  } else {
+    // TODO: if adding a local reference failing causes the VM to abort
+    // then the following check will never occur.
+    jthrowable localException = AddLocalReference<jthrowable>(ts, exception);
+    if (localException == NULL) {
+      // We were unable to add a new local reference, and threw a new
+      // exception.  We can't return "exception", because it's not a
+      // local reference.  So we have to return NULL, indicating that
+      // there was no exception, even though it's pretty much raining
+      // exceptions in here.
+      LOG(WARNING) << "JNI WARNING: addLocal/exception combo";
+    }
+    return localException;
+  }
 }
 
 void ExceptionDescribe(JNIEnv* env) {
@@ -381,10 +396,19 @@
   return NULL;
 }
 
-jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass clazz) {
+jboolean IsInstanceOf(JNIEnv* env, jobject jobj, jclass clazz) {
   ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNI_FALSE;
+  CHECK_NE(static_cast<jclass>(NULL), clazz);
+  if (jobj == NULL) {
+    // NB. JNI is different from regular Java instanceof in this respect
+    return JNI_TRUE;
+  } else {
+    // TODO: retrieve handle value for object
+    Object* obj = reinterpret_cast<Object*>(jobj);
+    // TODO: retrieve handle value for class
+    Class* klass = reinterpret_cast<Class*>(clazz);
+    return Object::InstanceOf(obj, klass) ? JNI_TRUE : JNI_FALSE;
+  }
 }
 
 jmethodID GetMethodID(JNIEnv* env,
@@ -401,17 +425,31 @@
     // private methods and constructors.
     method = klass->FindDeclaredDirectMethod(name, sig);
   }
-  if (method == NULL || method->IsStatic()) {
-    LG << "NoSuchMethodError";  // TODO: throw NoSuchMethodError
+  if (method == NULL) {
+    Thread* self = Thread::Current();
+    std::string class_name = klass->GetDescriptor().ToString();
+    // TODO: pretty print method names through a single routine
+    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+                            "no method \"%s.%s%s\"",
+                            class_name.c_str(), name, sig);
     return NULL;
-  }
-  // TODO: create a JNI weak global reference for method
-  bool success = EnsureInvokeStub(method);
-  if (!success) {
-    // TODO: throw OutOfMemoryException
+  } else if (method->IsStatic()) {
+    Thread* self = Thread::Current();
+    std::string class_name = klass->GetDescriptor().ToString();
+    // TODO: pretty print method names through a single routine
+    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+                            "method \"%s.%s%s\" is static",
+                            class_name.c_str(), name, sig);
     return NULL;
+  } else {
+    // TODO: create a JNI weak global reference for method
+    bool success = EnsureInvokeStub(method);
+    if (!success) {
+      // TODO: throw OutOfMemoryException
+      return NULL;
+    }
+    return reinterpret_cast<jmethodID>(method);
   }
-  return reinterpret_cast<jmethodID>(method);
 }
 
 jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
@@ -933,17 +971,33 @@
     // TODO: initialize the class
   }
   Method* method = klass->FindDirectMethod(name, sig);
-  if (method == NULL || !method->IsStatic()) {
-    LG << "NoSuchMethodError";  // TODO: throw NoSuchMethodError
+  if (method == NULL) {
+    Thread* self = Thread::Current();
+    std::string class_name = klass->GetDescriptor().ToString();
+    // TODO: pretty print method names through a single routine
+    // TODO: may want to FindVirtualMethod to give more informative error
+    // message here
+    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+                            "no method \"%s.%s%s\"",
+                            class_name.c_str(), name, sig);
     return NULL;
-  }
-  // TODO: create a JNI weak global reference for method
-  bool success = EnsureInvokeStub(method);
-  if (!success) {
-    // TODO: throw OutOfMemoryException
+  } else if (!method->IsStatic()) {
+    Thread* self = Thread::Current();
+    std::string class_name = klass->GetDescriptor().ToString();
+    // TODO: pretty print method names through a single routine
+    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+                            "method \"%s.%s%s\" is not static",
+                            class_name.c_str(), name, sig);
     return NULL;
+  } else {
+    // TODO: create a JNI weak global reference for method
+    bool success = EnsureInvokeStub(method);
+    if (!success) {
+      // TODO: throw OutOfMemoryException
+      return NULL;
+    }
+    return reinterpret_cast<jmethodID>(method);
   }
-  return reinterpret_cast<jmethodID>(method);
 }
 
 jobject CallStaticObjectMethod(JNIEnv* env,
@@ -1597,8 +1651,35 @@
 jint RegisterNatives(JNIEnv* env,
     jclass clazz, const JNINativeMethod* methods, jint nMethods) {
   ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
+  // TODO: retrieve handle value for class
+  Class* klass = reinterpret_cast<Class*>(clazz);
+  for(int i = 0; i < nMethods; i++) {
+    const char* name = methods[i].name;
+    const char* sig = methods[i].signature;
+    Method* method = klass->FindDirectMethod(name, sig);
+    if (method == NULL) {
+      method = klass->FindVirtualMethod(name, sig);
+    }
+    if (method == NULL) {
+      Thread* self = Thread::Current();
+      std::string class_name = klass->GetDescriptor().ToString();
+      // TODO: pretty print method names through a single routine
+      self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+                              "no method \"%s.%s%s\"",
+                              class_name.c_str(), name, sig);
+      return JNI_ERR;
+    } else if (!method->IsNative()) {
+      Thread* self = Thread::Current();
+      std::string class_name = klass->GetDescriptor().ToString();
+      // TODO: pretty print method names through a single routine
+      self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+                              "method \"%s.%s%s\" is not native",
+                              class_name.c_str(), name, sig);
+      return JNI_ERR;
+    }
+    method->RegisterNative(methods[i].fnPtr);
+  }
+  return JNI_OK;
 }
 
 jint UnregisterNatives(JNIEnv* env, jclass clazz) {
@@ -1943,26 +2024,15 @@
   GetObjectRefType,
 };
 
-void MonitorEnterHelper(JNIEnv* env, jobject obj) {
-  CHECK_EQ(Thread::Current()->GetJniEnv(), env);
-  MonitorEnter(env, obj);  // Ignore the result.
-}
+static const size_t kMonitorTableInitialSize = 32; // Arbitrary.
+static const size_t kMonitorTableMaxSize = 4096; // Arbitrary sanity check.
 
-void MonitorExitHelper(JNIEnv* env, jobject obj) {
-  CHECK_EQ(Thread::Current()->GetJniEnv(), env);
-  MonitorExit(env, obj);  // Ignore the result.
-}
-
-JNIEnv* CreateJNIEnv() {
-  Thread* self = Thread::Current();
-  CHECK(self != NULL);
-  JNIEnvExt* result = new JNIEnvExt;
-  result->fns = &gNativeInterface;
-  result->self = self;
-  result->critical = false;
-  result->MonitorEnterHelper = &MonitorEnterHelper;
-  result->MonitorExitHelper = &MonitorExitHelper;
-  return reinterpret_cast<JNIEnv*>(result);
+JNIEnvExt::JNIEnvExt(Thread* self, bool check_jni)
+    : fns(&gNativeInterface),
+      self(self),
+      check_jni(check_jni),
+      critical(false),
+      monitor_table("monitor table", kMonitorTableInitialSize, kMonitorTableMaxSize) {
 }
 
 // JNI Invocation interface.
@@ -2094,11 +2164,14 @@
   AttachCurrentThreadAsDaemon
 };
 
-JavaVM* CreateJavaVM(Runtime* runtime) {
-  JavaVMExt* result = new JavaVMExt;
-  result->fns = &gInvokeInterface;
-  result->runtime = runtime;
-  return reinterpret_cast<JavaVM*>(result);
+static const size_t kPinTableInitialSize = 16;
+static const size_t kPinTableMaxSize = 1024;
+
+JavaVMExt::JavaVMExt(Runtime* runtime, bool check_jni)
+    : fns(&gInvokeInterface),
+      runtime(runtime),
+      check_jni(check_jni),
+      pin_table("pin table", kPinTableInitialSize, kPinTableMaxSize) {
 }
 
 }  // namespace art
diff --git a/src/jni_internal.h b/src/jni_internal.h
index 3ff12d8..9cf8d57 100644
--- a/src/jni_internal.h
+++ b/src/jni_internal.h
@@ -7,35 +7,42 @@
 
 #include "assembler.h"
 #include "macros.h"
+#include "reference_table.h"
 
 namespace art {
 
 class Runtime;
 class Thread;
 
-JavaVM* CreateJavaVM(Runtime* runtime);
-JNIEnv* CreateJNIEnv();
-
 struct JavaVMExt {
+  JavaVMExt(Runtime* runtime, bool check_jni);
+
   // Must be first to correspond with JNIEnv.
   const struct JNIInvokeInterface* fns;
 
   Runtime* runtime;
+
+  bool check_jni;
+
+  // Used to hold references to pinned primitive arrays.
+  ReferenceTable pin_table;
 };
 
 struct JNIEnvExt {
+  JNIEnvExt(Thread* self, bool check_jni);
+
   // Must be first to correspond with JavaVM.
   const struct JNINativeInterface* fns;
 
   Thread* self;
 
+  bool check_jni;
+
   // Are we in a "critical" JNI call?
   bool critical;
 
-  // Used to help call synchronized native methods.
-  // TODO: make jni_compiler.cc do the indirection itself.
-  void (*MonitorEnterHelper)(JNIEnv*, jobject);
-  void (*MonitorExitHelper)(JNIEnv*, jobject);
+  // Entered JNI monitors, for bulk exit on thread detach.
+  ReferenceTable  monitor_table;
 };
 
 }  // namespace art
diff --git a/src/jni_internal_test.cc b/src/jni_internal_test.cc
index 569dfda..04b3225 100644
--- a/src/jni_internal_test.cc
+++ b/src/jni_internal_test.cc
@@ -57,6 +57,117 @@
   EXPECT_CLASS_NOT_FOUND("K");
 }
 
+TEST_F(JniInternalTest, GetMethodID) {
+  jclass jlobject = env_->FindClass("java/lang/Object");
+  jclass jlstring = env_->FindClass("java/lang/String");
+  jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
+
+  // Sanity check that no exceptions are pending
+  EXPECT_FALSE(env_->ExceptionCheck());
+
+  // Check that java.lang.Object.foo() doesn't exist and NoSuchMethodError is
+  // a pending exception
+  jmethodID method = env_->GetMethodID(jlobject, "foo", "()V");
+  EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+  EXPECT_TRUE(env_->ExceptionCheck());
+  jthrowable exception = env_->ExceptionOccurred();
+  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+  env_->ExceptionClear();
+
+  // Check that java.lang.Object.equals() does exist
+#if defined(__arm__)
+  method = env_->GetMethodID(jlobject, "equals", "(Ljava/lang/Object;)Z");
+  EXPECT_NE(static_cast<jmethodID>(NULL), method);
+  EXPECT_FALSE(env_->ExceptionCheck());
+#endif
+
+  // Check that GetMethodID for java.lang.String.valueOf(int) fails as the
+  // method is static
+  method = env_->GetMethodID(jlstring, "valueOf", "(I)Ljava/lang/String;");
+  EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+  EXPECT_TRUE(env_->ExceptionCheck());
+  exception = env_->ExceptionOccurred();
+  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+  env_->ExceptionClear();
+}
+
+TEST_F(JniInternalTest, GetStaticMethodID) {
+  jclass jlobject = env_->FindClass("java/lang/Object");
+  jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
+
+  // Sanity check that no exceptions are pending
+  EXPECT_FALSE(env_->ExceptionCheck());
+
+  // Check that java.lang.Object.foo() doesn't exist and NoSuchMethodError is
+  // a pending exception
+  jmethodID method = env_->GetStaticMethodID(jlobject, "foo", "()V");
+  EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+  EXPECT_TRUE(env_->ExceptionCheck());
+  jthrowable exception = env_->ExceptionOccurred();
+  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+  env_->ExceptionClear();
+
+  // Check that GetStaticMethodID for java.lang.Object.equals(Object) fails as
+  // the method is not static
+  method = env_->GetStaticMethodID(jlobject, "equals", "(Ljava/lang/Object;)Z");
+  EXPECT_EQ(static_cast<jmethodID>(NULL), method);
+  EXPECT_TRUE(env_->ExceptionCheck());
+  exception = env_->ExceptionOccurred();
+  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+  env_->ExceptionClear();
+
+  // Check that java.lang.String.valueOf(int) does exist
+#if defined(__arm__)
+  jclass jlstring = env_->FindClass("java/lang/String");
+  method = env_->GetStaticMethodID(jlstring, "valueOf",
+                                   "(I)Ljava/lang/String;");
+  EXPECT_NE(static_cast<jmethodID>(NULL), method);
+  EXPECT_FALSE(env_->ExceptionCheck());
+#endif
+}
+
+TEST_F(JniInternalTest, RegisterNatives) {
+  jclass jlobject = env_->FindClass("java/lang/Object");
+  jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
+
+  // Sanity check that no exceptions are pending
+  EXPECT_FALSE(env_->ExceptionCheck());
+
+  // Check that registering to a non-existent java.lang.Object.foo() causes a
+  // NoSuchMethodError
+  {
+    JNINativeMethod methods[] = {{"foo", "()V", NULL}};
+    env_->RegisterNatives(jlobject, methods, 1);
+  }
+  EXPECT_TRUE(env_->ExceptionCheck());
+  jthrowable exception = env_->ExceptionOccurred();
+  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+  env_->ExceptionClear();
+
+  // Check that registering non-native methods causes a NoSuchMethodError
+  {
+    JNINativeMethod methods[] = {{"equals", "(Ljava/lang/Object;)Z", NULL}};
+    env_->RegisterNatives(jlobject, methods, 1);
+  }
+  EXPECT_TRUE(env_->ExceptionCheck());
+  exception = env_->ExceptionOccurred();
+  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
+  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
+  env_->ExceptionClear();
+
+  // Check that registering native methods is successful
+  {
+    JNINativeMethod methods[] = {{"hashCode", "()I", NULL}};
+    env_->RegisterNatives(jlobject, methods, 1);
+  }
+  EXPECT_FALSE(env_->ExceptionCheck());
+}
+
 TEST_F(JniInternalTest, NewPrimitiveArray) {
   // TODO: death tests for negative array sizes.
 
diff --git a/src/runtime.cc b/src/runtime.cc
index 2ad3589..2476837 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -180,6 +180,13 @@
   scoped_ptr<ParsedOptions> parsed(new ParsedOptions());
   const char* boot_class_path = getenv("BOOTCLASSPATH");
   parsed->boot_image_ = NULL;
+#ifdef NDEBUG
+  // CheckJNI is off by default for regular builds...
+  parsed->check_jni_ = false;
+#else
+  // ...but on by default in debug builds.
+  parsed->check_jni_ = true;
+#endif
   parsed->heap_initial_size_ = Heap::kInitialSize;
   parsed->heap_maximum_size_ = Heap::kMaximumSize;
   parsed->hook_vfprintf_ = vfprintf;
@@ -194,6 +201,8 @@
       parsed->boot_class_path_ = *reinterpret_cast<const std::vector<DexFile*>*>(options[i].second);
     } else if (option.starts_with("-Xbootimage:")) {
       parsed->boot_image_ = option.substr(strlen("-Xbootimage:")).data();
+    } else if (option.starts_with("-Xcheck:jni")) {
+      parsed->check_jni_ = true;
     } else if (option.starts_with("-Xms")) {
       parsed->heap_initial_size_ = ParseMemoryOption(option.substr(strlen("-Xms")).data(), 1024);
     } else if (option.starts_with("-Xmx")) {
@@ -262,23 +271,24 @@
   Heap::Init(parsed_options->heap_initial_size_,
              parsed_options->heap_maximum_size_);
 
+  java_vm_.reset(reinterpret_cast<JavaVM*>(new JavaVMExt(this, parsed_options->check_jni_)));
+
   Thread::Init();
-  Thread* current_thread = Thread::Attach();
+  Thread* current_thread = Thread::Attach(this);
   thread_list_->Register(current_thread);
 
   class_linker_ = ClassLinker::Create(parsed_options->boot_class_path_);
 
-  java_vm_.reset(CreateJavaVM(this));
   return true;
 }
 
 bool Runtime::AttachCurrentThread(const char* name, JNIEnv** penv) {
-  return Thread::Attach() != NULL;
+  return Thread::Attach(instance_) != NULL;
 }
 
 bool Runtime::AttachCurrentThreadAsDaemon(const char* name, JNIEnv** penv) {
   // TODO: do something different for daemon threads.
-  return Thread::Attach() != NULL;
+  return Thread::Attach(instance_) != NULL;
 }
 
 bool Runtime::DetachCurrentThread() {
diff --git a/src/runtime.h b/src/runtime.h
index da46ee6..828e677 100644
--- a/src/runtime.h
+++ b/src/runtime.h
@@ -33,6 +33,7 @@
 
     std::vector<DexFile*> boot_class_path_;
     const char* boot_image_;
+    bool check_jni_;
     size_t heap_initial_size_;
     size_t heap_maximum_size_;
     jint (*hook_vfprintf_)(FILE* stream, const char* format, va_list ap);
@@ -76,7 +77,7 @@
     return class_linker_;
   }
 
-  JavaVM* GetJavaVM() {
+  JavaVM* GetJavaVM() const {
     return java_vm_.get();
   }
 
diff --git a/src/thread.cc b/src/thread.cc
index 609ae41..baa659c 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -85,7 +85,7 @@
   return new_thread;
 }
 
-Thread* Thread::Attach() {
+Thread* Thread::Attach(const Runtime* runtime) {
   Thread* thread = new Thread;
   thread->InitCpu();
   thread->stack_limit_ = reinterpret_cast<byte*>(-1);  // TODO: getrlimit
@@ -103,7 +103,10 @@
       PLOG(FATAL) << "pthread_setspecific failed";
   }
 
-  thread->jni_env_ = CreateJNIEnv();
+  JavaVMExt* vm = reinterpret_cast<JavaVMExt*>(runtime->GetJavaVM());
+  CHECK(vm != NULL);
+  bool check_jni = vm->check_jni;
+  thread->jni_env_ = reinterpret_cast<JNIEnv*>(new JNIEnvExt(thread, check_jni));
 
   return thread;
 }
diff --git a/src/thread.h b/src/thread.h
index 8bde370..de5893e 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -118,7 +118,7 @@
   static Thread* Create(size_t stack_size);
 
   // Creates a new thread from the calling thread.
-  static Thread* Attach();
+  static Thread* Attach(const Runtime* runtime);
 
   static Thread* Current() {
     void* thread = pthread_getspecific(Thread::pthread_key_self_);