Wire up check JNI force copy mode.

Increase check JNI checks.
Break apart jni_internal.h in to jni_env_ext.h and java_vm_ext.h.
Fix the abuse of ScopedObjectAccess/annotalysis by ScopedCheck in the case
of VM routines.
Make class loader override and shared library class loader JNI global
references rather than mirror pointers.
Clean-ups to native bridge.

Change-Id: If7c6110b5aade7a402bfb67534af86a7b2cdeb55
diff --git a/runtime/check_jni.cc b/runtime/check_jni.cc
index a530594..99277a0 100644
--- a/runtime/check_jni.cc
+++ b/runtime/check_jni.cc
@@ -25,6 +25,7 @@
 #include "dex_file-inl.h"
 #include "field_helper.h"
 #include "gc/space/space.h"
+#include "java_vm_ext.h"
 #include "mirror/art_field-inl.h"
 #include "mirror/art_method-inl.h"
 #include "mirror/class-inl.h"
@@ -35,62 +36,16 @@
 #include "runtime.h"
 #include "scoped_thread_state_change.h"
 #include "thread.h"
+#include "well_known_classes.h"
 
 namespace art {
 
-static void JniAbort(const char* jni_function_name, const char* msg) {
-  Thread* self = Thread::Current();
-  ScopedObjectAccess soa(self);
-  mirror::ArtMethod* current_method = self->GetCurrentMethod(nullptr);
-
-  std::ostringstream os;
-  os << "JNI DETECTED ERROR IN APPLICATION: " << msg;
-
-  if (jni_function_name != nullptr) {
-    os << "\n    in call to " << jni_function_name;
-  }
-  // TODO: is this useful given that we're about to dump the calling thread's stack?
-  if (current_method != nullptr) {
-    os << "\n    from " << PrettyMethod(current_method);
-  }
-  os << "\n";
-  self->Dump(os);
-
-  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
-  if (vm->check_jni_abort_hook != nullptr) {
-    vm->check_jni_abort_hook(vm->check_jni_abort_hook_data, os.str());
-  } else {
-    // Ensure that we get a native stack trace for this thread.
-    self->TransitionFromRunnableToSuspended(kNative);
-    LOG(FATAL) << os.str();
-    self->TransitionFromSuspendedToRunnable();  // Unreachable, keep annotalysis happy.
-  }
-}
-
-static void JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) {
-  std::string msg;
-  StringAppendV(&msg, fmt, ap);
-  JniAbort(jni_function_name, msg.c_str());
-}
-
-void JniAbortF(const char* jni_function_name, const char* fmt, ...) {
-  va_list args;
-  va_start(args, fmt);
-  JniAbortV(jni_function_name, fmt, args);
-  va_end(args);
-}
-
 /*
  * ===========================================================================
  *      JNI function helpers
  * ===========================================================================
  */
 
-static bool IsHandleScopeLocalRef(JNIEnv* env, jobject localRef) {
-  return GetIndirectRefKind(localRef) == kHandleScopeOrInvalid &&
-      reinterpret_cast<JNIEnvExt*>(env)->self->HandleScopeContains(localRef);
-}
-
 // Flags passed into ScopedCheck.
 #define kFlag_Default       0x0000
 
@@ -109,134 +64,88 @@
 #define kFlag_Invocation    0x8000      // Part of the invocation interface (JavaVM*).
 
 #define kFlag_ForceTrace    0x80000000  // Add this to a JNI function's flags if you want to trace every call.
-
-static const char* gBuiltInPrefixes[] = {
-  "Landroid/",
-  "Lcom/android/",
-  "Lcom/google/android/",
-  "Ldalvik/",
-  "Ljava/",
-  "Ljavax/",
-  "Llibcore/",
-  "Lorg/apache/harmony/",
-  nullptr
+/*
+ * Java primitive types:
+ * B - jbyte
+ * C - jchar
+ * D - jdouble
+ * F - jfloat
+ * I - jint
+ * J - jlong
+ * S - jshort
+ * Z - jboolean (shown as true and false)
+ * V - void
+ *
+ * Java reference types:
+ * L - jobject
+ * a - jarray
+ * c - jclass
+ * s - jstring
+ * t - jthrowable
+ *
+ * JNI types:
+ * b - jboolean (shown as JNI_TRUE and JNI_FALSE)
+ * f - jfieldID
+ * i - JNI error value (JNI_OK, JNI_ERR, JNI_EDETACHED, JNI_EVERSION)
+ * m - jmethodID
+ * p - void*
+ * r - jint (for release mode arguments)
+ * u - const char* (Modified UTF-8)
+ * z - jsize (for lengths; use i if negative values are okay)
+ * v - JavaVM*
+ * w - jobjectRefType
+ * E - JNIEnv*
+ * . - no argument; just print "..." (used for varargs JNI calls)
+ *
+ */
+union JniValueType {
+  jarray a;
+  jboolean b;
+  jclass c;
+  jfieldID f;
+  jint i;
+  jmethodID m;
+  const void* p;  // Pointer.
+  jint r;  // Release mode.
+  jstring s;
+  jthrowable t;
+  const char* u;  // Modified UTF-8.
+  JavaVM* v;
+  jobjectRefType w;
+  jsize z;
+  jbyte B;
+  jchar C;
+  jdouble D;
+  JNIEnv* E;
+  jfloat F;
+  jint I;
+  jlong J;
+  jobject L;
+  jshort S;
+  const void* V;  // void
+  jboolean Z;
 };
 
-static bool ShouldTrace(JavaVMExt* vm, mirror::ArtMethod* method)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  // If both "-Xcheck:jni" and "-Xjnitrace:" are enabled, we print trace messages
-  // when a native method that matches the -Xjnitrace argument calls a JNI function
-  // such as NewByteArray.
-  // If -verbose:third-party-jni is on, we want to log any JNI function calls
-  // made by a third-party native method.
-  std::string class_name(method->GetDeclaringClassDescriptor());
-  if (!vm->trace.empty() && class_name.find(vm->trace) != std::string::npos) {
-    return true;
-  }
-  if (VLOG_IS_ON(third_party_jni)) {
-    // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look
-    // like part of Android.
-    for (size_t i = 0; gBuiltInPrefixes[i] != nullptr; ++i) {
-      if (StartsWith(class_name, gBuiltInPrefixes[i])) {
-        return false;
-      }
-    }
-    return true;
-  }
-  return false;
-}
-
 class ScopedCheck {
  public:
-  // For JNIEnv* functions.
-  explicit ScopedCheck(JNIEnv* env, int flags, const char* functionName)
-      SHARED_LOCK_FUNCTION(Locks::mutator_lock_)
-      : soa_(env) {
-    Init(flags, functionName, true);
-    CheckThread(flags);
+  explicit ScopedCheck(int flags, const char* functionName, bool has_method = true)
+      : function_name_(functionName), flags_(flags), indent_(0), has_method_(has_method) {
   }
 
-  // For JavaVM* functions.
-  // TODO: it's not correct that this is a lock function, but making it so aids annotalysis.
-  explicit ScopedCheck(JavaVM* vm, bool has_method, const char* functionName)
-      SHARED_LOCK_FUNCTION(Locks::mutator_lock_)
-      : soa_(vm) {
-    Init(kFlag_Invocation, functionName, has_method);
-  }
-
-  ~ScopedCheck() UNLOCK_FUNCTION(Locks::mutator_lock_) {}
-
-  const ScopedObjectAccess& soa() {
-    return soa_;
-  }
-
-  bool ForceCopy() {
-    return Runtime::Current()->GetJavaVM()->force_copy;
-  }
+  ~ScopedCheck() {}
 
   // Checks that 'class_name' is a valid "fully-qualified" JNI class name, like "java/lang/Thread"
   // or "[Ljava/lang/Object;". A ClassLoader can actually normalize class names a couple of
   // times, so using "java.lang.Thread" instead of "java/lang/Thread" might work in some
   // circumstances, but this is incorrect.
-  void CheckClassName(const char* class_name) {
+  bool CheckClassName(const char* class_name) {
     if ((class_name == nullptr) || !IsValidJniClassName(class_name)) {
-      JniAbortF(function_name_,
-                "illegal class name '%s'\n"
-                "    (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')",
-                class_name);
+      AbortF("illegal class name '%s'\n"
+             "    (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')",
+             class_name);
+      return false;
     }
-  }
-
-  /*
-   * Verify that the field is of the appropriate type.  If the field has an
-   * object type, "java_object" is the object we're trying to assign into it.
-   *
-   * Works for both static and instance fields.
-   */
-  void CheckFieldType(jvalue value, jfieldID fid, char prim, bool isStatic)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    StackHandleScope<1> hs(Thread::Current());
-    Handle<mirror::ArtField> f(hs.NewHandle(CheckFieldID(fid)));
-    if (f.Get() == nullptr) {
-      return;
-    }
-    mirror::Class* field_type = FieldHelper(f).GetType();
-    if (!field_type->IsPrimitive()) {
-      jobject java_object = value.l;
-      if (java_object != nullptr) {
-        mirror::Object* obj = soa_.Decode<mirror::Object*>(java_object);
-        // If java_object is a weak global ref whose referent has been cleared,
-        // obj will be NULL.  Otherwise, obj should always be non-NULL
-        // and valid.
-        if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(obj)) {
-          Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-          JniAbortF(function_name_, "field operation on invalid %s: %p",
-                    ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
-          return;
-        } else {
-          if (!obj->InstanceOf(field_type)) {
-            JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %s",
-                      PrettyField(f.Get()).c_str(), PrettyTypeOf(obj).c_str());
-            return;
-          }
-        }
-      }
-    } else if (field_type != Runtime::Current()->GetClassLinker()->FindPrimitiveClass(prim)) {
-      JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %c",
-                PrettyField(f.Get()).c_str(), prim);
-      return;
-    }
-
-    if (isStatic != f.Get()->IsStatic()) {
-      if (isStatic) {
-        JniAbortF(function_name_, "accessing non-static field %s as static",
-                  PrettyField(f.Get()).c_str());
-      } else {
-        JniAbortF(function_name_, "accessing static field %s as non-static",
-                  PrettyField(f.Get()).c_str());
-      }
-      return;
-    }
+    return true;
   }
 
   /*
@@ -244,59 +153,87 @@
    *
    * Assumes "jobj" has already been validated.
    */
-  void CheckInstanceFieldID(jobject java_object, jfieldID fid)
+  bool CheckInstanceFieldID(ScopedObjectAccess& soa, jobject java_object, jfieldID fid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::Object* o = soa_.Decode<mirror::Object*>(java_object);
-    if (o == nullptr || !Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) {
+    mirror::Object* o = soa.Decode<mirror::Object*>(java_object);
+    if (o == nullptr) {
+      AbortF("field operation on NULL object: %p", java_object);
+      return false;
+    }
+    if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "field operation on invalid %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
-      return;
+      AbortF("field operation on invalid %s: %p",
+             ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(),
+             java_object);
+      return false;
     }
 
-    mirror::ArtField* f = CheckFieldID(fid);
+    mirror::ArtField* f = CheckFieldID(soa, fid);
     if (f == nullptr) {
-      return;
+      return false;
     }
     mirror::Class* c = o->GetClass();
     if (c->FindInstanceField(f->GetName(), f->GetTypeDescriptor()) == nullptr) {
-      JniAbortF(function_name_, "jfieldID %s not valid for an object of class %s",
-                PrettyField(f).c_str(), PrettyTypeOf(o).c_str());
+      AbortF("jfieldID %s not valid for an object of class %s",
+             PrettyField(f).c_str(), PrettyTypeOf(o).c_str());
+      return false;
     }
+    return true;
   }
 
   /*
    * Verify that the pointer value is non-NULL.
    */
-  void CheckNonNull(const void* ptr) {
-    if (ptr == nullptr) {
-      JniAbortF(function_name_, "non-nullable argument was NULL");
+  bool CheckNonNull(const void* ptr) {
+    if (UNLIKELY(ptr == nullptr)) {
+      AbortF("non-nullable argument was NULL");
+      return false;
     }
+    return true;
   }
 
   /*
    * Verify that the method's return type matches the type of call.
    * 'expectedType' will be "L" for all objects, including arrays.
    */
-  void CheckSig(jmethodID mid, const char* expectedType, bool isStatic)
+  bool CheckMethodAndSig(ScopedObjectAccess& soa, jobject jobj, jclass jc,
+                         jmethodID mid, Primitive::Type type, InvokeType invoke)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::ArtMethod* m = CheckMethodID(mid);
+    mirror::ArtMethod* m = CheckMethodID(soa, mid);
     if (m == nullptr) {
-      return;
+      return false;
     }
-    if (*expectedType != m->GetShorty()[0]) {
-      JniAbortF(function_name_, "the return type of %s does not match %s",
-                function_name_, PrettyMethod(m).c_str());
+    if (type != Primitive::GetType(m->GetShorty()[0])) {
+      AbortF("the return type of %s does not match %s", function_name_, PrettyMethod(m).c_str());
+      return false;
     }
-    if (isStatic != m->IsStatic()) {
-      if (isStatic) {
-        JniAbortF(function_name_, "calling non-static method %s with %s",
-                  PrettyMethod(m).c_str(), function_name_);
+    bool is_static = (invoke == kStatic);
+    if (is_static != m->IsStatic()) {
+      if (is_static) {
+        AbortF("calling non-static method %s with %s",
+               PrettyMethod(m).c_str(), function_name_);
       } else {
-        JniAbortF(function_name_, "calling static method %s with %s",
-                  PrettyMethod(m).c_str(), function_name_);
+        AbortF("calling static method %s with %s",
+               PrettyMethod(m).c_str(), function_name_);
+      }
+      return false;
+    }
+    if (invoke != kVirtual) {
+      mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+      if (!m->GetDeclaringClass()->IsAssignableFrom(c)) {
+        AbortF("can't call %s %s with class %s", invoke == kStatic ? "static" : "nonvirtual",
+            PrettyMethod(m).c_str(), PrettyClass(c).c_str());
+        return false;
       }
     }
+    if (invoke != kStatic) {
+      mirror::Object* o = soa.Decode<mirror::Object*>(jobj);
+      if (!o->InstanceOf(m->GetDeclaringClass())) {
+        AbortF("can't call %s on instance of %s", PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str());
+        return false;
+      }
+    }
+    return true;
   }
 
   /*
@@ -304,17 +241,18 @@
    *
    * Assumes "java_class" has already been validated.
    */
-  void CheckStaticFieldID(jclass java_class, jfieldID fid)
+  bool CheckStaticFieldID(ScopedObjectAccess& soa, jclass java_class, jfieldID fid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::Class* c = soa_.Decode<mirror::Class*>(java_class);
-    mirror::ArtField* f = CheckFieldID(fid);
+    mirror::Class* c = soa.Decode<mirror::Class*>(java_class);
+    mirror::ArtField* f = CheckFieldID(soa, fid);
     if (f == nullptr) {
-      return;
+      return false;
     }
     if (f->GetDeclaringClass() != c) {
-      JniAbortF(function_name_, "static jfieldID %p not valid for class %s",
-                fid, PrettyClass(c).c_str());
+      AbortF("static jfieldID %p not valid for class %s", fid, PrettyClass(c).c_str());
+      return false;
     }
+    return true;
   }
 
   /*
@@ -326,17 +264,18 @@
    *
    * Instances of "java_class" must be instances of the method's declaring class.
    */
-  void CheckStaticMethod(jclass java_class, jmethodID mid)
+  bool CheckStaticMethod(ScopedObjectAccess& soa, jclass java_class, jmethodID mid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::ArtMethod* m = CheckMethodID(mid);
+    mirror::ArtMethod* m = CheckMethodID(soa, mid);
     if (m == nullptr) {
-      return;
+      return false;
     }
-    mirror::Class* c = soa_.Decode<mirror::Class*>(java_class);
+    mirror::Class* c = soa.Decode<mirror::Class*>(java_class);
     if (!m->GetDeclaringClass()->IsAssignableFrom(c)) {
-      JniAbortF(function_name_, "can't call static %s on class %s",
-                PrettyMethod(m).c_str(), PrettyClass(c).c_str());
+      AbortF("can't call static %s on class %s", PrettyMethod(m).c_str(), PrettyClass(c).c_str());
+      return false;
     }
+    return true;
   }
 
   /*
@@ -346,17 +285,18 @@
    * (Note the mid might point to a declaration in an interface; this
    * will be handled automatically by the instanceof check.)
    */
-  void CheckVirtualMethod(jobject java_object, jmethodID mid)
+  bool CheckVirtualMethod(ScopedObjectAccess& soa, jobject java_object, jmethodID mid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::ArtMethod* m = CheckMethodID(mid);
+    mirror::ArtMethod* m = CheckMethodID(soa, mid);
     if (m == nullptr) {
-      return;
+      return false;
     }
-    mirror::Object* o = soa_.Decode<mirror::Object*>(java_object);
+    mirror::Object* o = soa.Decode<mirror::Object*>(java_object);
     if (!o->InstanceOf(m->GetDeclaringClass())) {
-      JniAbortF(function_name_, "can't call %s on instance of %s",
-                PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str());
+      AbortF("can't call %s on instance of %s", PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str());
+      return false;
     }
+    return true;
   }
 
   /**
@@ -395,11 +335,10 @@
    *
    * Use the kFlag_NullableUtf flag where 'u' field(s) are nullable.
    */
-  void Check(bool entry, const char* fmt0, ...) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    va_list ap;
-
+  bool Check(ScopedObjectAccess& soa, bool entry, const char* fmt, JniValueType* args)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     mirror::ArtMethod* traceMethod = nullptr;
-    if (has_method_ && (!soa_.Vm()->trace.empty() || VLOG_IS_ON(third_party_jni))) {
+    if (has_method_ && soa.Vm()->IsTracingEnabled()) {
       // We need to guard some of the invocation interface's calls: a bad caller might
       // use DetachCurrentThread or GetEnv on a thread that's not yet attached.
       Thread* self = Thread::Current();
@@ -409,124 +348,14 @@
     }
 
     if (((flags_ & kFlag_ForceTrace) != 0) ||
-        (traceMethod != nullptr && ShouldTrace(soa_.Vm(), traceMethod))) {
-      va_start(ap, fmt0);
+        (traceMethod != nullptr && soa.Vm()->ShouldTrace(traceMethod))) {
       std::string msg;
-      for (const char* fmt = fmt0; *fmt;) {
-        char ch = *fmt++;
-        if (ch == 'B') {  // jbyte
-          jbyte b = va_arg(ap, int);
-          if (b >= 0 && b < 10) {
-            StringAppendF(&msg, "%d", b);
-          } else {
-            StringAppendF(&msg, "%#x (%d)", b, b);
-          }
-        } else if (ch == 'C') {  // jchar
-          jchar c = va_arg(ap, int);
-          if (c < 0x7f && c >= ' ') {
-            StringAppendF(&msg, "U+%x ('%c')", c, c);
-          } else {
-            StringAppendF(&msg, "U+%x", c);
-          }
-        } else if (ch == 'F' || ch == 'D') {  // jfloat, jdouble
-          StringAppendF(&msg, "%g", va_arg(ap, double));
-        } else if (ch == 'I' || ch == 'S') {  // jint, jshort
-          StringAppendF(&msg, "%d", va_arg(ap, int));
-        } else if (ch == 'J') {  // jlong
-          StringAppendF(&msg, "%" PRId64, va_arg(ap, jlong));
-        } else if (ch == 'Z') {  // jboolean
-          StringAppendF(&msg, "%s", va_arg(ap, int) ? "true" : "false");
-        } else if (ch == 'V') {  // void
-          msg += "void";
-        } else if (ch == 'v') {  // JavaVM*
-          JavaVM* vm = va_arg(ap, JavaVM*);
-          StringAppendF(&msg, "(JavaVM*)%p", vm);
-        } else if (ch == 'E') {  // JNIEnv*
-          JNIEnv* env = va_arg(ap, JNIEnv*);
-          StringAppendF(&msg, "(JNIEnv*)%p", env);
-        } else if (ch == 'L' || ch == 'a' || ch == 's') {  // jobject, jarray, jstring
-          // For logging purposes, these are identical.
-          jobject o = va_arg(ap, jobject);
-          if (o == nullptr) {
-            msg += "NULL";
-          } else {
-            StringAppendF(&msg, "%p", o);
-          }
-        } else if (ch == 'b') {  // jboolean (JNI-style)
-          jboolean b = va_arg(ap, int);
-          msg += (b ? "JNI_TRUE" : "JNI_FALSE");
-        } else if (ch == 'c') {  // jclass
-          jclass jc = va_arg(ap, jclass);
-          mirror::Class* c = reinterpret_cast<mirror::Class*>(Thread::Current()->DecodeJObject(jc));
-          if (c == nullptr) {
-            msg += "NULL";
-          } else if (c == kInvalidIndirectRefObject ||
-              !Runtime::Current()->GetHeap()->IsValidObjectAddress(c)) {
-            StringAppendF(&msg, "INVALID POINTER:%p", jc);
-          } else if (!c->IsClass()) {
-            msg += "INVALID NON-CLASS OBJECT OF TYPE:" + PrettyTypeOf(c);
-          } else {
-            msg += PrettyClass(c);
-            if (!entry) {
-              StringAppendF(&msg, " (%p)", jc);
-            }
-          }
-        } else if (ch == 'f') {  // jfieldID
-          jfieldID fid = va_arg(ap, jfieldID);
-          mirror::ArtField* f = reinterpret_cast<mirror::ArtField*>(fid);
-          msg += PrettyField(f);
-          if (!entry) {
-            StringAppendF(&msg, " (%p)", fid);
-          }
-        } else if (ch == 'z') {  // non-negative jsize
-          // You might expect jsize to be size_t, but it's not; it's the same as jint.
-          // We only treat this specially so we can do the non-negative check.
-          // TODO: maybe this wasn't worth it?
-          jint i = va_arg(ap, jint);
-          StringAppendF(&msg, "%d", i);
-        } else if (ch == 'm') {  // jmethodID
-          jmethodID mid = va_arg(ap, jmethodID);
-          mirror::ArtMethod* m = reinterpret_cast<mirror::ArtMethod*>(mid);
-          msg += PrettyMethod(m);
-          if (!entry) {
-            StringAppendF(&msg, " (%p)", mid);
-          }
-        } else if (ch == 'p') {  // void* ("pointer")
-          void* p = va_arg(ap, void*);
-          if (p == nullptr) {
-            msg += "NULL";
-          } else {
-            StringAppendF(&msg, "(void*) %p", p);
-          }
-        } else if (ch == 'r') {  // jint (release mode)
-          jint releaseMode = va_arg(ap, jint);
-          if (releaseMode == 0) {
-            msg += "0";
-          } else if (releaseMode == JNI_ABORT) {
-            msg += "JNI_ABORT";
-          } else if (releaseMode == JNI_COMMIT) {
-            msg += "JNI_COMMIT";
-          } else {
-            StringAppendF(&msg, "invalid release mode %d", releaseMode);
-          }
-        } else if (ch == 'u') {  // const char* (Modified UTF-8)
-          const char* utf = va_arg(ap, const char*);
-          if (utf == nullptr) {
-            msg += "NULL";
-          } else {
-            StringAppendF(&msg, "\"%s\"", utf);
-          }
-        } else if (ch == '.') {
-          msg += "...";
-        } else {
-          JniAbortF(function_name_, "unknown trace format specifier: %c", ch);
-          return;
-        }
-        if (*fmt) {
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        TracePossibleHeapValue(soa, entry, fmt[i], args[i], &msg);
+        if (fmt[i + 1] != '\0') {
           StringAppendF(&msg, ", ");
         }
       }
-      va_end(ap);
 
       if ((flags_ & kFlag_ForceTrace) != 0) {
         LOG(INFO) << "JNI: call to " << function_name_ << "(" << msg << ")";
@@ -546,43 +375,227 @@
 
     // We always do the thorough checks on entry, and never on exit...
     if (entry) {
-      va_start(ap, fmt0);
-      for (const char* fmt = fmt0; *fmt; ++fmt) {
-        char ch = *fmt;
-        if (ch == 'a') {
-          CheckArray(va_arg(ap, jarray));
-        } else if (ch == 'c') {
-          CheckInstance(kClass, va_arg(ap, jclass));
-        } else if (ch == 'L') {
-          CheckObject(va_arg(ap, jobject));
-        } else if (ch == 'r') {
-          CheckReleaseMode(va_arg(ap, jint));
-        } else if (ch == 's') {
-          CheckInstance(kString, va_arg(ap, jstring));
-        } else if (ch == 'u') {
-          if ((flags_ & kFlag_Release) != 0) {
-            CheckNonNull(va_arg(ap, const char*));
-          } else {
-            bool nullable = ((flags_ & kFlag_NullableUtf) != 0);
-            CheckUtfString(va_arg(ap, const char*), nullable);
-          }
-        } else if (ch == 'z') {
-          CheckLengthPositive(va_arg(ap, jsize));
-        } else if (strchr("BCISZbfmpEv", ch) != nullptr) {
-          va_arg(ap, uint32_t);  // Skip this argument.
-        } else if (ch == 'D' || ch == 'F') {
-          va_arg(ap, double);  // Skip this argument.
-        } else if (ch == 'J') {
-          va_arg(ap, uint64_t);  // Skip this argument.
-        } else if (ch == '.') {
-        } else {
-          LOG(FATAL) << "Unknown check format specifier: " << ch;
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        if (!CheckPossibleHeapValue(soa, fmt[i], args[i])) {
+          return false;
         }
       }
-      va_end(ap);
     }
+    return true;
   }
 
+  bool CheckNonHeap(JavaVMExt* vm, bool entry, const char* fmt, JniValueType* args) {
+    bool should_trace = (flags_ & kFlag_ForceTrace) != 0;
+    if (!should_trace && vm->IsTracingEnabled()) {
+      // We need to guard some of the invocation interface's calls: a bad caller might
+      // use DetachCurrentThread or GetEnv on a thread that's not yet attached.
+      Thread* self = Thread::Current();
+      if ((flags_ & kFlag_Invocation) == 0 || self != nullptr) {
+        ScopedObjectAccess soa(self);
+        mirror::ArtMethod* traceMethod = self->GetCurrentMethod(nullptr);
+        should_trace = (traceMethod != nullptr && vm->ShouldTrace(traceMethod));
+      }
+    }
+    if (should_trace) {
+      std::string msg;
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        TraceNonHeapValue(fmt[i], args[i], &msg);
+        if (fmt[i + 1] != '\0') {
+          StringAppendF(&msg, ", ");
+        }
+      }
+
+      if ((flags_ & kFlag_ForceTrace) != 0) {
+        LOG(INFO) << "JNI: call to " << function_name_ << "(" << msg << ")";
+      } else if (entry) {
+        if (has_method_) {
+          Thread* self = Thread::Current();
+          ScopedObjectAccess soa(self);
+          mirror::ArtMethod* traceMethod = self->GetCurrentMethod(nullptr);
+          std::string methodName(PrettyMethod(traceMethod, false));
+          LOG(INFO) << "JNI: " << methodName << " -> " << function_name_ << "(" << msg << ")";
+          indent_ = methodName.size() + 1;
+        } else {
+          LOG(INFO) << "JNI: -> " << function_name_ << "(" << msg << ")";
+          indent_ = 0;
+        }
+      } else {
+        LOG(INFO) << StringPrintf("JNI: %*s<- %s returned %s", indent_, "", function_name_, msg.c_str());
+      }
+    }
+
+    // We always do the thorough checks on entry, and never on exit...
+    if (entry) {
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        if (!CheckNonHeapValue(fmt[i], args[i])) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  bool CheckReflectedMethod(ScopedObjectAccess& soa, jobject jmethod)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Object* method = soa.Decode<mirror::Object*>(jmethod);
+    if (method == nullptr) {
+      AbortF("expected non-null method");
+      return false;
+    }
+    mirror::Class* c = method->GetClass();
+    if (soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Method) != c &&
+        soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Constructor) != c) {
+      AbortF("expected java.lang.reflect.Method or "
+          "java.lang.reflect.Constructor but got object of type %s: %p",
+          PrettyTypeOf(method).c_str(), jmethod);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckConstructor(ScopedObjectAccess& soa, jmethodID mid)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::ArtMethod* method = soa.DecodeMethod(mid);
+    if (method == nullptr) {
+      AbortF("expected non-null constructor");
+      return false;
+    }
+    if (!method->IsConstructor() || method->IsStatic()) {
+      AbortF("expected a constructor but %s: %p", PrettyTypeOf(method).c_str(), mid);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckReflectedField(ScopedObjectAccess& soa, jobject jfield)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Object* field = soa.Decode<mirror::Object*>(jfield);
+    if (field == nullptr) {
+      AbortF("expected non-null java.lang.reflect.Field");
+      return false;
+    }
+    mirror::Class* c = field->GetClass();
+    if (soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Field) != c) {
+      AbortF("expected java.lang.reflect.Field but got object of type %s: %p",
+             PrettyTypeOf(field).c_str(), jfield);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckThrowable(ScopedObjectAccess& soa, jthrowable jobj)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Object* obj = soa.Decode<mirror::Object*>(jobj);
+    if (!obj->GetClass()->IsThrowableClass()) {
+      AbortF("expected java.lang.Throwable but got object of type "
+             "%s: %p", PrettyTypeOf(obj).c_str(), obj);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckThrowableClass(ScopedObjectAccess& soa, jclass jc)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+    if (!c->IsThrowableClass()) {
+      AbortF("expected java.lang.Throwable class but got object of "
+             "type %s: %p", PrettyDescriptor(c).c_str(), c);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckReferenceKind(IndirectRefKind expected_kind, JavaVMExt* vm, Thread* self, jobject obj) {
+    IndirectRefKind found_kind;
+    if (expected_kind == kLocal) {
+      found_kind = GetIndirectRefKind(obj);
+      if (found_kind == kHandleScopeOrInvalid && self->HandleScopeContains(obj)) {
+        found_kind = kLocal;
+      }
+    } else {
+      found_kind = GetIndirectRefKind(obj);
+    }
+    if (obj != nullptr && found_kind != expected_kind) {
+      AbortF("expected reference of kind %s but found %s: %p",
+             ToStr<IndirectRefKind>(expected_kind).c_str(),
+             ToStr<IndirectRefKind>(GetIndirectRefKind(obj)).c_str(),
+             obj);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckInstantiableNonArray(ScopedObjectAccess& soa, jclass jc)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+    if (!c->IsInstantiableNonArray()) {
+      AbortF("can't make objects of type %s: %p", PrettyDescriptor(c).c_str(), c);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckPrimitiveArrayType(ScopedObjectAccess& soa, jarray array, Primitive::Type type)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (!CheckArray(soa, array)) {
+      return false;
+    }
+    mirror::Array* a = soa.Decode<mirror::Array*>(array);
+    if (a->GetClass()->GetComponentType()->GetPrimitiveType() != type) {
+      AbortF("incompatible array type %s expected %s[]: %p",
+             PrettyDescriptor(a->GetClass()).c_str(), PrettyDescriptor(type).c_str(), array);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckFieldAccess(ScopedObjectAccess& soa, jobject obj, jfieldID fid, bool is_static,
+                        Primitive::Type type)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (is_static && !CheckStaticFieldID(soa, down_cast<jclass>(obj), fid)) {
+      return false;
+    }
+    if (!is_static && !CheckInstanceFieldID(soa, obj, fid)) {
+      return false;
+    }
+    mirror::ArtField* field = soa.DecodeField(fid);
+    DCHECK(field != nullptr);  // Already checked by Check.
+    if (is_static != field->IsStatic()) {
+      AbortF("attempt to access %s field %s: %p",
+             field->IsStatic() ? "static" : "non-static", PrettyField(field).c_str(), fid);
+      return false;
+    }
+    if (type != field->GetTypeAsPrimitiveType()) {
+      AbortF("attempt to access field %s of type %s with the wrong type %s: %p",
+             PrettyField(field).c_str(), PrettyDescriptor(field->GetTypeDescriptor()).c_str(),
+             PrettyDescriptor(type).c_str(), fid);
+      return false;
+    }
+    if (is_static) {
+      mirror::Object* o = soa.Decode<mirror::Object*>(obj);
+      if (o == nullptr || !o->IsClass()) {
+        AbortF("attempt to access static field %s with a class argument of type %s: %p",
+               PrettyField(field).c_str(), PrettyTypeOf(o).c_str(), fid);
+        return false;
+      }
+      mirror::Class* c = o->AsClass();
+      if (field->GetDeclaringClass() != c) {
+        AbortF("attempt to access static field %s with an incompatible class argument of %s: %p",
+               PrettyField(field).c_str(), PrettyDescriptor(c).c_str(), fid);
+        return false;
+      }
+    } else {
+      mirror::Object* o = soa.Decode<mirror::Object*>(obj);
+      if (o == nullptr || !field->GetDeclaringClass()->IsAssignableFrom(o->GetClass())) {
+        AbortF("attempt to access field %s from an object argument of type %s: %p",
+               PrettyField(field).c_str(), PrettyTypeOf(o).c_str(), fid);
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
   enum InstanceKind {
     kClass,
     kDirectByteBuffer,
@@ -598,7 +611,7 @@
    * Because we're looking at an object on the GC heap, we have to switch
    * to "running" mode before doing the checks.
    */
-  bool CheckInstance(InstanceKind kind, jobject java_object)
+  bool CheckInstance(ScopedObjectAccess& soa, InstanceKind kind, jobject java_object, bool null_ok)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     const char* what = nullptr;
     switch (kind) {
@@ -622,15 +635,20 @@
     }
 
     if (java_object == nullptr) {
-      JniAbortF(function_name_, "%s received null %s", function_name_, what);
-      return false;
+      if (null_ok) {
+        return true;
+      } else {
+        AbortF("%s received NULL %s", function_name_, what);
+        return false;
+      }
     }
 
-    mirror::Object* obj = soa_.Decode<mirror::Object*>(java_object);
+    mirror::Object* obj = soa.Decode<mirror::Object*>(java_object);
     if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(obj)) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "%s is an invalid %s: %p (%p)",
-                what, ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object, obj);
+      AbortF("%s is an invalid %s: %p (%p)",
+             what, ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(),
+             java_object, obj);
       return false;
     }
 
@@ -652,114 +670,333 @@
       break;
     }
     if (!okay) {
-      JniAbortF(function_name_, "%s has wrong type: %s", what, PrettyTypeOf(obj).c_str());
+      AbortF("%s has wrong type: %s", what, PrettyTypeOf(obj).c_str());
       return false;
     }
 
     return true;
   }
 
- private:
-  // Set "has_method" to true if we have a valid thread with a method pointer.
-  // We won't have one before attaching a thread, after detaching a thread, or
-  // when shutting down the runtime.
-  void Init(int flags, const char* functionName, bool has_method) {
-    flags_ = flags;
-    function_name_ = functionName;
-    has_method_ = has_method;
+  /*
+   * Verify that the "mode" argument passed to a primitive array Release
+   * function is one of the valid values.
+   */
+  bool CheckReleaseMode(jint mode) {
+    if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) {
+      AbortF("unknown value for release mode: %d", mode);
+      return false;
+    }
+    return true;
   }
 
+  bool CheckPossibleHeapValue(ScopedObjectAccess& soa, char fmt, JniValueType arg)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    switch (fmt) {
+      case 'a':  // jarray
+        return CheckArray(soa, arg.a);
+      case 'c':  // jclass
+        return CheckInstance(soa, kClass, arg.c, false);
+      case 'f':  // jfieldID
+        return CheckFieldID(soa, arg.f) != nullptr;
+      case 'm':  // jmethodID
+        return CheckMethodID(soa, arg.m) != nullptr;
+      case 'r':  // release int
+        return CheckReleaseMode(arg.r);
+      case 's':  // jstring
+        return CheckInstance(soa, kString, arg.s, false);
+      case 't':  // jthrowable
+        return CheckInstance(soa, kThrowable, arg.t, false);
+      case 'E':  // JNIEnv*
+        return CheckThread(arg.E);
+      case 'L':  // jobject
+        return CheckInstance(soa, kObject, arg.L, true);
+      default:
+        return CheckNonHeapValue(fmt, arg);
+    }
+  }
+
+  bool CheckNonHeapValue(char fmt, JniValueType arg) {
+    switch (fmt) {
+      case '.':  // ...
+      case 'p':  // TODO: pointer - null or readable?
+      case 'v':  // JavaVM*
+      case 'B':  // jbyte
+      case 'C':  // jchar
+      case 'D':  // jdouble
+      case 'F':  // jfloat
+      case 'I':  // jint
+      case 'J':  // jlong
+      case 'S':  // jshort
+        break;  // Ignored.
+      case 'b':  // jboolean, why two? Fall-through.
+      case 'Z':
+        return CheckBoolean(arg.Z);
+      case 'u':  // utf8
+        if ((flags_ & kFlag_Release) != 0) {
+          return CheckNonNull(arg.u);
+        } else {
+          bool nullable = ((flags_ & kFlag_NullableUtf) != 0);
+          return CheckUtfString(arg.u, nullable);
+        }
+      case 'w':  // jobjectRefType
+        switch (arg.w) {
+          case JNIInvalidRefType:
+          case JNILocalRefType:
+          case JNIGlobalRefType:
+          case JNIWeakGlobalRefType:
+            break;
+          default:
+            AbortF("Unknown reference type");
+            return false;
+        }
+        break;
+      case 'z':  // jsize
+        return CheckLengthPositive(arg.z);
+      default:
+        AbortF("unknown format specifier: '%c'", fmt);
+        return false;
+    }
+    return true;
+  }
+
+  void TracePossibleHeapValue(ScopedObjectAccess& soa, bool entry, char fmt, JniValueType arg,
+                              std::string* msg)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    switch (fmt) {
+      case 'L':  // jobject fall-through.
+      case 'a':  // jarray fall-through.
+      case 's':  // jstring fall-through.
+      case 't':  // jthrowable fall-through.
+        if (arg.L == nullptr) {
+          *msg += "NULL";
+        } else {
+          StringAppendF(msg, "%p", arg.L);
+        }
+        break;
+      case 'c': {  // jclass
+        jclass jc = arg.c;
+        mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+        if (c == nullptr) {
+          *msg += "NULL";
+        } else if (c == kInvalidIndirectRefObject ||
+            !Runtime::Current()->GetHeap()->IsValidObjectAddress(c)) {
+          StringAppendF(msg, "INVALID POINTER:%p", jc);
+        } else if (!c->IsClass()) {
+          *msg += "INVALID NON-CLASS OBJECT OF TYPE:" + PrettyTypeOf(c);
+        } else {
+          *msg += PrettyClass(c);
+          if (!entry) {
+            StringAppendF(msg, " (%p)", jc);
+          }
+        }
+        break;
+      }
+      case 'f': {  // jfieldID
+        jfieldID fid = arg.f;
+        mirror::ArtField* f = soa.DecodeField(fid);
+        *msg += PrettyField(f);
+        if (!entry) {
+          StringAppendF(msg, " (%p)", fid);
+        }
+        break;
+      }
+      case 'm': {  // jmethodID
+        jmethodID mid = arg.m;
+        mirror::ArtMethod* m = soa.DecodeMethod(mid);
+        *msg += PrettyMethod(m);
+        if (!entry) {
+          StringAppendF(msg, " (%p)", mid);
+        }
+        break;
+      }
+      default:
+        TraceNonHeapValue(fmt, arg, msg);
+        break;
+    }
+  }
+
+  void TraceNonHeapValue(char fmt, JniValueType arg, std::string* msg) {
+    switch (fmt) {
+      case 'B':  // jbyte
+        if (arg.B >= 0 && arg.B < 10) {
+          StringAppendF(msg, "%d", arg.B);
+        } else {
+          StringAppendF(msg, "%#x (%d)", arg.B, arg.B);
+        }
+        break;
+      case 'C':  // jchar
+        if (arg.C < 0x7f && arg.C >= ' ') {
+          StringAppendF(msg, "U+%x ('%c')", arg.C, arg.C);
+        } else {
+          StringAppendF(msg, "U+%x", arg.C);
+        }
+        break;
+      case 'F':  // jfloat
+        StringAppendF(msg, "%g", arg.F);
+        break;
+      case 'D':  // jdouble
+        StringAppendF(msg, "%g", arg.D);
+        break;
+      case 'S':  // jshort
+        StringAppendF(msg, "%d", arg.S);
+        break;
+      case 'i':  // jint - fall-through.
+      case 'I':  // jint
+        StringAppendF(msg, "%d", arg.I);
+        break;
+      case 'J':  // jlong
+        StringAppendF(msg, "%" PRId64, arg.J);
+        break;
+      case 'Z':  // jboolean
+      case 'b':  // jboolean (JNI-style)
+        *msg += arg.b == JNI_TRUE ? "true" : "false";
+        break;
+      case 'V':  // void
+        DCHECK(arg.V == nullptr);
+        *msg += "void";
+        break;
+      case 'v':  // JavaVM*
+        StringAppendF(msg, "(JavaVM*)%p", arg.v);
+        break;
+      case 'E':
+        StringAppendF(msg, "(JNIEnv*)%p", arg.E);
+        break;
+      case 'z':  // non-negative jsize
+        // You might expect jsize to be size_t, but it's not; it's the same as jint.
+        // We only treat this specially so we can do the non-negative check.
+        // TODO: maybe this wasn't worth it?
+        StringAppendF(msg, "%d", arg.z);
+        break;
+      case 'p':  // void* ("pointer")
+        if (arg.p == nullptr) {
+          *msg += "NULL";
+        } else {
+          StringAppendF(msg, "(void*) %p", arg.p);
+        }
+        break;
+      case 'r': {  // jint (release mode)
+        jint releaseMode = arg.r;
+        if (releaseMode == 0) {
+          *msg += "0";
+        } else if (releaseMode == JNI_ABORT) {
+          *msg += "JNI_ABORT";
+        } else if (releaseMode == JNI_COMMIT) {
+          *msg += "JNI_COMMIT";
+        } else {
+          StringAppendF(msg, "invalid release mode %d", releaseMode);
+        }
+        break;
+      }
+      case 'u':  // const char* (Modified UTF-8)
+        if (arg.u == nullptr) {
+          *msg += "NULL";
+        } else {
+          StringAppendF(msg, "\"%s\"", arg.u);
+        }
+        break;
+      case 'w':  // jobjectRefType
+        switch (arg.w) {
+          case JNIInvalidRefType:
+            *msg += "invalid reference type";
+            break;
+          case JNILocalRefType:
+            *msg += "local ref type";
+            break;
+          case JNIGlobalRefType:
+            *msg += "global ref type";
+            break;
+          case JNIWeakGlobalRefType:
+            *msg += "weak global ref type";
+            break;
+          default:
+            *msg += "unknown ref type";
+            break;
+        }
+        break;
+      case '.':
+        *msg += "...";
+        break;
+      default:
+        LOG(FATAL) << function_name_ << ": unknown trace format specifier: '" << fmt << "'";
+    }
+  }
   /*
    * Verify that "array" is non-NULL and points to an Array object.
    *
    * Since we're dealing with objects, switch to "running" mode.
    */
-  void CheckArray(jarray java_array) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    if (java_array == nullptr) {
-      JniAbortF(function_name_, "jarray was NULL");
-      return;
+  bool CheckArray(ScopedObjectAccess& soa, jarray java_array)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (UNLIKELY(java_array == nullptr)) {
+      AbortF("jarray was NULL");
+      return false;
     }
 
-    mirror::Array* a = soa_.Decode<mirror::Array*>(java_array);
-    if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(a)) {
+    mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
+    if (UNLIKELY(!Runtime::Current()->GetHeap()->IsValidObjectAddress(a))) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "jarray is an invalid %s: %p (%p)",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(java_array)).c_str(), java_array, a);
+      AbortF("jarray is an invalid %s: %p (%p)",
+             ToStr<IndirectRefKind>(GetIndirectRefKind(java_array)).c_str(),
+             java_array, a);
+      return false;
     } else if (!a->IsArrayInstance()) {
-      JniAbortF(function_name_, "jarray argument has non-array type: %s", PrettyTypeOf(a).c_str());
+      AbortF("jarray argument has non-array type: %s", PrettyTypeOf(a).c_str());
+      return false;
     }
+    return true;
   }
 
-  void CheckLengthPositive(jsize length) {
+  bool CheckBoolean(jboolean z) {
+    if (z != JNI_TRUE && z != JNI_FALSE) {
+      AbortF("unexpected jboolean value: %d", z);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckLengthPositive(jsize length) {
     if (length < 0) {
-      JniAbortF(function_name_, "negative jsize: %d", length);
+      AbortF("negative jsize: %d", length);
+      return false;
     }
+    return true;
   }
 
-  mirror::ArtField* CheckFieldID(jfieldID fid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  mirror::ArtField* CheckFieldID(ScopedObjectAccess& soa, jfieldID fid)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     if (fid == nullptr) {
-      JniAbortF(function_name_, "jfieldID was NULL");
+      AbortF("jfieldID was NULL");
       return nullptr;
     }
-    mirror::ArtField* f = soa_.DecodeField(fid);
+    mirror::ArtField* f = soa.DecodeField(fid);
     if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(f) || !f->IsArtField()) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "invalid jfieldID: %p", fid);
+      AbortF("invalid jfieldID: %p", fid);
       return nullptr;
     }
     return f;
   }
 
-  mirror::ArtMethod* CheckMethodID(jmethodID mid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  mirror::ArtMethod* CheckMethodID(ScopedObjectAccess& soa, jmethodID mid)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     if (mid == nullptr) {
-      JniAbortF(function_name_, "jmethodID was NULL");
+      AbortF("jmethodID was NULL");
       return nullptr;
     }
-    mirror::ArtMethod* m = soa_.DecodeMethod(mid);
+    mirror::ArtMethod* m = soa.DecodeMethod(mid);
     if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(m) || !m->IsArtMethod()) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "invalid jmethodID: %p", mid);
+      AbortF("invalid jmethodID: %p", mid);
       return nullptr;
     }
     return m;
   }
 
-  /*
-   * Verify that "jobj" is a valid object, and that it's an object that JNI
-   * is allowed to know about.  We allow NULL references.
-   *
-   * Switches to "running" mode before performing checks.
-   */
-  void CheckObject(jobject java_object)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    if (java_object == nullptr) {
-      return;
-    }
-
-    mirror::Object* o = soa_.Decode<mirror::Object*>(java_object);
-    if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) {
-      Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      // TODO: when we remove work_around_app_jni_bugs, this should be impossible.
-      JniAbortF(function_name_, "native code passing in reference to invalid %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
-    }
-  }
-
-  /*
-   * Verify that the "mode" argument passed to a primitive array Release
-   * function is one of the valid values.
-   */
-  void CheckReleaseMode(jint mode) {
-    if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) {
-      JniAbortF(function_name_, "unknown value for release mode: %d", mode);
-    }
-  }
-
-  void CheckThread(int flags) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  bool CheckThread(JNIEnv* env) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     Thread* self = Thread::Current();
     if (self == nullptr) {
-      JniAbortF(function_name_, "a thread (tid %d) is making JNI calls without being attached", GetTid());
-      return;
+      AbortF("a thread (tid %d) is making JNI calls without being attached", GetTid());
+      return false;
     }
 
     // Get the *correct* JNIEnv by going through our TLS pointer.
@@ -767,21 +1004,22 @@
 
     // Verify that the current thread is (a) attached and (b) associated with
     // this particular instance of JNIEnv.
-    if (soa_.Env() != threadEnv) {
-      JniAbortF(function_name_, "thread %s using JNIEnv* from thread %s",
-                ToStr<Thread>(*self).c_str(), ToStr<Thread>(*soa_.Self()).c_str());
-      return;
+    if (env != threadEnv) {
+      AbortF("thread %s using JNIEnv* from thread %s",
+             ToStr<Thread>(*self).c_str(), ToStr<Thread>(*self).c_str());
+      return false;
     }
 
     // Verify that, if this thread previously made a critical "get" call, we
     // do the corresponding "release" call before we try anything else.
-    switch (flags & kFlag_CritMask) {
+    switch (flags_ & kFlag_CritMask) {
     case kFlag_CritOkay:    // okay to call this method
       break;
     case kFlag_CritBad:     // not okay to call
       if (threadEnv->critical) {
-        JniAbortF(function_name_, "thread %s using JNI after critical get", ToStr<Thread>(*self).c_str());
-        return;
+        AbortF("thread %s using JNI after critical get",
+               ToStr<Thread>(*self).c_str());
+        return false;
       }
       break;
     case kFlag_CritGet:     // this is a "get" call
@@ -791,44 +1029,46 @@
     case kFlag_CritRelease:  // this is a "release" call
       threadEnv->critical--;
       if (threadEnv->critical < 0) {
-        JniAbortF(function_name_, "thread %s called too many critical releases", ToStr<Thread>(*self).c_str());
-        return;
+        AbortF("thread %s called too many critical releases",
+               ToStr<Thread>(*self).c_str());
+        return false;
       }
       break;
     default:
-      LOG(FATAL) << "Bad flags (internal error): " << flags;
+      LOG(FATAL) << "Bad flags (internal error): " << flags_;
     }
 
     // Verify that, if an exception has been raised, the native code doesn't
     // make any JNI calls other than the Exception* methods.
-    if ((flags & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) {
+    if ((flags_ & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) {
       ThrowLocation throw_location;
       mirror::Throwable* exception = self->GetException(&throw_location);
       std::string type(PrettyTypeOf(exception));
-      JniAbortF(function_name_, "JNI %s called with pending exception '%s' thrown in %s",
-                function_name_, type.c_str(), throw_location.Dump().c_str());
-      return;
+      AbortF("JNI %s called with pending exception '%s' thrown in %s",
+             function_name_, type.c_str(), throw_location.Dump().c_str());
+      return false;
     }
+    return true;
   }
 
   // Verifies that "bytes" points to valid Modified UTF-8 data.
-  void CheckUtfString(const char* bytes, bool nullable) {
+  bool CheckUtfString(const char* bytes, bool nullable) {
     if (bytes == nullptr) {
       if (!nullable) {
-        JniAbortF(function_name_, "non-nullable const char* was NULL");
-        return;
+        AbortF("non-nullable const char* was NULL");
+        return false;
       }
-      return;
+      return true;
     }
 
     const char* errorKind = nullptr;
     uint8_t utf8 = CheckUtfBytes(bytes, &errorKind);
     if (errorKind != nullptr) {
-      JniAbortF(function_name_,
-                "input is not valid Modified UTF-8: illegal %s byte %#x\n"
-                "    string: '%s'", errorKind, utf8, bytes);
-      return;
+      AbortF("input is not valid Modified UTF-8: illegal %s byte %#x\n"
+          "    string: '%s'", errorKind, utf8, bytes);
+      return false;
     }
+    return true;
   }
 
   static uint8_t CheckUtfBytes(const char* bytes, const char** errorKind) {
@@ -880,92 +1120,120 @@
     return 0;
   }
 
-  const ScopedObjectAccess soa_;
-  const char* function_name_;
-  int flags_;
-  bool has_method_;
+  void AbortF(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+    va_list args;
+    va_start(args, fmt);
+    Runtime::Current()->GetJavaVM()->JniAbortV(function_name_, fmt, args);
+    va_end(args);
+  }
+
+  // The name of the JNI function being checked.
+  const char* const function_name_;
+
+  const int flags_;
   int indent_;
 
+  const bool has_method_;
+
   DISALLOW_COPY_AND_ASSIGN(ScopedCheck);
 };
 
-#define CHECK_JNI_ENTRY(flags, types, args...) \
-  ScopedCheck sc(env, flags, __FUNCTION__); \
-  sc.Check(true, types, ##args)
-
-#define CHECK_JNI_EXIT(type, exp) ({ \
-    auto _rc = (exp); \
-  sc.Check(false, type, _rc); \
-  _rc; })
-#define CHECK_JNI_EXIT_VOID() \
-  sc.Check(false, "V")
-
 /*
  * ===========================================================================
  *      Guarded arrays
  * ===========================================================================
  */
 
-#define kGuardLen       512         /* must be multiple of 2 */
-#define kGuardPattern   0xd5e3      /* uncommon values; d5e3d5e3 invalid addr */
-#define kGuardMagic     0xffd5aa96
-
 /* this gets tucked in at the start of the buffer; struct size must be even */
-struct GuardedCopy {
-  uint32_t magic;
-  uLong adler;
-  size_t original_length;
-  const void* original_ptr;
-
-  /* find the GuardedCopy given the pointer into the "live" data */
-  static inline const GuardedCopy* FromData(const void* dataBuf) {
-    return reinterpret_cast<const GuardedCopy*>(ActualBuffer(dataBuf));
-  }
-
+class GuardedCopy {
+ public:
   /*
    * Create an over-sized buffer to hold the contents of "buf".  Copy it in,
    * filling in the area around it with guard data.
-   *
-   * We use a 16-bit pattern to make a rogue memset less likely to elude us.
    */
-  static void* Create(const void* buf, size_t len, bool modOkay) {
-    size_t newLen = ActualLength(len);
-    uint8_t* newBuf = DebugAlloc(newLen);
-
-    // Fill it in with a pattern.
-    uint16_t* pat = reinterpret_cast<uint16_t*>(newBuf);
-    for (size_t i = 0; i < newLen / 2; i++) {
-      *pat++ = kGuardPattern;
-    }
-
-    // Copy the data in; note "len" could be zero.
-    memcpy(newBuf + kGuardLen / 2, buf, len);
+  static void* Create(const void* original_buf, size_t len, bool mod_okay) {
+    const size_t new_len = LengthIncludingRedZones(len);
+    uint8_t* const new_buf = DebugAlloc(new_len);
 
     // If modification is not expected, grab a checksum.
     uLong adler = 0;
-    if (!modOkay) {
-      adler = adler32(0L, Z_NULL, 0);
-      adler = adler32(adler, reinterpret_cast<const Bytef*>(buf), len);
-      *reinterpret_cast<uLong*>(newBuf) = adler;
+    if (!mod_okay) {
+      adler = adler32(adler32(0L, Z_NULL, 0), reinterpret_cast<const Bytef*>(original_buf), len);
     }
 
-    GuardedCopy* pExtra = reinterpret_cast<GuardedCopy*>(newBuf);
-    pExtra->magic = kGuardMagic;
-    pExtra->adler = adler;
-    pExtra->original_ptr = buf;
-    pExtra->original_length = len;
+    GuardedCopy* copy = new (new_buf) GuardedCopy(original_buf, len, adler);
 
-    return newBuf + kGuardLen / 2;
+    // Fill begin region with canary pattern.
+    const size_t kStartCanaryLength = (GuardedCopy::kRedZoneSize / 2) - sizeof(GuardedCopy);
+    for (size_t i = 0, j = 0; i < kStartCanaryLength; ++i) {
+      const_cast<char*>(copy->StartRedZone())[i] = kCanary[j];
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+
+    // Copy the data in; note "len" could be zero.
+    memcpy(const_cast<uint8_t*>(copy->BufferWithinRedZones()), original_buf, len);
+
+    // Fill end region with canary pattern.
+    for (size_t i = 0, j = 0; i < kEndCanaryLength; ++i) {
+      const_cast<char*>(copy->EndRedZone())[i] = kCanary[j];
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+
+    return const_cast<uint8_t*>(copy->BufferWithinRedZones());
   }
 
   /*
+   * Create a guarded copy of a primitive array.  Modifications to the copied
+   * data are allowed.  Returns a pointer to the copied data.
+   */
+  static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* is_copy) {
+    ScopedObjectAccess soa(env);
+
+    mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
+    size_t component_size = a->GetClass()->GetComponentSize();
+    size_t byte_count = a->GetLength() * component_size;
+    void* result = Create(a->GetRawData(component_size, 0), byte_count, true);
+    if (is_copy != nullptr) {
+      *is_copy = JNI_TRUE;
+    }
+    return result;
+  }
+
+  /*
+   * Perform the array "release" operation, which may or may not copy data
+   * back into the managed heap, and may or may not release the underlying storage.
+   */
+  static void* ReleaseGuardedPACopy(const char* function_name, JNIEnv* env, jarray java_array,
+                                   void* embedded_buf, int mode) {
+    ScopedObjectAccess soa(env);
+    mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
+
+    if (!GuardedCopy::Check(function_name, embedded_buf, true)) {
+      return nullptr;
+    }
+    if (mode != JNI_ABORT) {
+      size_t len = FromEmbedded(embedded_buf)->original_length_;
+      memcpy(a->GetRawData(a->GetClass()->GetComponentSize(), 0), embedded_buf, len);
+    }
+    if (mode != JNI_COMMIT) {
+      return Destroy(embedded_buf);
+    }
+    return embedded_buf;
+  }
+
+
+  /*
    * Free up the guard buffer, scrub it, and return the original pointer.
    */
-  static void* Destroy(void* dataBuf) {
-    const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf);
-    void* original_ptr = const_cast<void*>(pExtra->original_ptr);
-    size_t len = pExtra->original_length;
-    DebugFree(dataBuf, len);
+  static void* Destroy(void* embedded_buf) {
+    GuardedCopy* copy = FromEmbedded(embedded_buf);
+    void* original_ptr = const_cast<void*>(copy->original_ptr_);
+    size_t len = LengthIncludingRedZones(copy->original_length_);
+    DebugFree(copy, len);
     return original_ptr;
   }
 
@@ -975,67 +1243,16 @@
    *
    * The caller has already checked that "dataBuf" is non-NULL.
    */
-  static void Check(const char* functionName, const void* dataBuf, bool modOkay) {
-    static const uint32_t kMagicCmp = kGuardMagic;
-    const uint8_t* fullBuf = ActualBuffer(dataBuf);
-    const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf);
-
-    // Before we do anything with "pExtra", check the magic number.  We
-    // do the check with memcmp rather than "==" in case the pointer is
-    // unaligned.  If it points to completely bogus memory we're going
-    // to crash, but there's no easy way around that.
-    if (memcmp(&pExtra->magic, &kMagicCmp, 4) != 0) {
-      uint8_t buf[4];
-      memcpy(buf, &pExtra->magic, 4);
-      JniAbortF(functionName,
-          "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?",
-          buf[3], buf[2], buf[1], buf[0], dataBuf);  // Assumes little-endian.
-    }
-
-    size_t len = pExtra->original_length;
-
-    // Check bottom half of guard; skip over optional checksum storage.
-    const uint16_t* pat = reinterpret_cast<const uint16_t*>(fullBuf);
-    for (size_t i = sizeof(GuardedCopy) / 2; i < (kGuardLen / 2 - sizeof(GuardedCopy)) / 2; i++) {
-      if (pat[i] != kGuardPattern) {
-        JniAbortF(functionName, "guard pattern(1) disturbed at %p +%zd", fullBuf, i*2);
-      }
-    }
-
-    int offset = kGuardLen / 2 + len;
-    if (offset & 0x01) {
-      // Odd byte; expected value depends on endian.
-      const uint16_t patSample = kGuardPattern;
-      uint8_t expected_byte = reinterpret_cast<const uint8_t*>(&patSample)[1];
-      if (fullBuf[offset] != expected_byte) {
-        JniAbortF(functionName, "guard pattern disturbed in odd byte after %p +%d 0x%02x 0x%02x",
-                  fullBuf, offset, fullBuf[offset], expected_byte);
-      }
-      offset++;
-    }
-
-    // Check top half of guard.
-    pat = reinterpret_cast<const uint16_t*>(fullBuf + offset);
-    for (size_t i = 0; i < kGuardLen / 4; i++) {
-      if (pat[i] != kGuardPattern) {
-        JniAbortF(functionName, "guard pattern(2) disturbed at %p +%zd", fullBuf, offset + i*2);
-      }
-    }
-
-    // If modification is not expected, verify checksum.  Strictly speaking
-    // this is wrong: if we told the client that we made a copy, there's no
-    // reason they can't alter the buffer.
-    if (!modOkay) {
-      uLong adler = adler32(0L, Z_NULL, 0);
-      adler = adler32(adler, (const Bytef*)dataBuf, len);
-      if (pExtra->adler != adler) {
-        JniAbortF(functionName, "buffer modified (0x%08lx vs 0x%08lx) at address %p",
-                  pExtra->adler, adler, dataBuf);
-      }
-    }
+  static bool Check(const char* function_name, const void* embedded_buf, bool mod_okay) {
+    const GuardedCopy* copy = FromEmbedded(embedded_buf);
+    return copy->CheckHeader(function_name, mod_okay) && copy->CheckRedZones(function_name);
   }
 
  private:
+  GuardedCopy(const void* original_buf, size_t len, uLong adler) :
+    magic_(kGuardMagic), adler_(adler), original_ptr_(original_buf), original_length_(len) {
+  }
+
   static uint8_t* DebugAlloc(size_t len) {
     void* result = mmap(nullptr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
     if (result == MAP_FAILED) {
@@ -1044,68 +1261,126 @@
     return reinterpret_cast<uint8_t*>(result);
   }
 
-  static void DebugFree(void* dataBuf, size_t len) {
-    uint8_t* fullBuf = ActualBuffer(dataBuf);
-    size_t totalByteCount = ActualLength(len);
-    // TODO: we could mprotect instead, and keep the allocation around for a while.
-    // This would be even more expensive, but it might catch more errors.
-    // if (mprotect(fullBuf, totalByteCount, PROT_NONE) != 0) {
-    //     PLOG(WARNING) << "mprotect(PROT_NONE) failed";
-    // }
-    if (munmap(fullBuf, totalByteCount) != 0) {
-      PLOG(FATAL) << "munmap(" << reinterpret_cast<void*>(fullBuf) << ", " << totalByteCount << ") failed";
+  static void DebugFree(void* buf, size_t len) {
+    if (munmap(buf, len) != 0) {
+      PLOG(FATAL) << "munmap(" << buf << ", " << len << ") failed";
     }
   }
 
-  static const uint8_t* ActualBuffer(const void* dataBuf) {
-    return reinterpret_cast<const uint8_t*>(dataBuf) - kGuardLen / 2;
+  static size_t LengthIncludingRedZones(size_t len) {
+    return len + kRedZoneSize;
   }
 
-  static uint8_t* ActualBuffer(void* dataBuf) {
-    return reinterpret_cast<uint8_t*>(dataBuf) - kGuardLen / 2;
+  // Get the GuardedCopy from the interior pointer.
+  static GuardedCopy* FromEmbedded(void* embedded_buf) {
+    return reinterpret_cast<GuardedCopy*>(
+        reinterpret_cast<uint8_t*>(embedded_buf) - (kRedZoneSize / 2));
   }
 
-  // Underlying length of a user allocation of 'length' bytes.
-  static size_t ActualLength(size_t length) {
-    return (length + kGuardLen + 1) & ~0x01;
+  static const GuardedCopy* FromEmbedded(const void* embedded_buf) {
+    return reinterpret_cast<const GuardedCopy*>(
+        reinterpret_cast<const uint8_t*>(embedded_buf) - (kRedZoneSize / 2));
   }
+
+  static void AbortF(const char* jni_function_name, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    Runtime::Current()->GetJavaVM()->JniAbortV(jni_function_name, fmt, args);
+    va_end(args);
+  }
+
+  bool CheckHeader(const char* function_name, bool mod_okay) const {
+    static const uint32_t kMagicCmp = kGuardMagic;
+
+    // Before we do anything with "pExtra", check the magic number.  We
+    // do the check with memcmp rather than "==" in case the pointer is
+    // unaligned.  If it points to completely bogus memory we're going
+    // to crash, but there's no easy way around that.
+    if (UNLIKELY(memcmp(&magic_, &kMagicCmp, 4) != 0)) {
+      uint8_t buf[4];
+      memcpy(buf, &magic_, 4);
+      AbortF(function_name,
+             "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?",
+             buf[3], buf[2], buf[1], buf[0], this);  // Assumes little-endian.
+      return false;
+    }
+
+    // If modification is not expected, verify checksum. Strictly speaking this is wrong: if we
+    // told the client that we made a copy, there's no reason they can't alter the buffer.
+    if (!mod_okay) {
+      uLong computed_adler =
+          adler32(adler32(0L, Z_NULL, 0), BufferWithinRedZones(), original_length_);
+      if (computed_adler != adler_) {
+        AbortF(function_name, "buffer modified (0x%08lx vs 0x%08lx) at address %p",
+               computed_adler, adler_, this);
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool CheckRedZones(const char* function_name) const {
+    // Check the begin red zone.
+    const size_t kStartCanaryLength = (GuardedCopy::kRedZoneSize / 2) - sizeof(GuardedCopy);
+    for (size_t i = 0, j = 0; i < kStartCanaryLength; ++i) {
+      if (UNLIKELY(StartRedZone()[i] != kCanary[j])) {
+        AbortF(function_name, "guard pattern before buffer disturbed at %p +%zd", this, i);
+        return false;
+      }
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+
+    // Check end region.
+    for (size_t i = 0, j = 0; i < kEndCanaryLength; ++i) {
+      if (UNLIKELY(EndRedZone()[i] != kCanary[j])) {
+        size_t offset_from_buffer_start =
+            &(EndRedZone()[i]) - &(StartRedZone()[kStartCanaryLength]);
+        AbortF(function_name, "guard pattern after buffer disturbed at %p +%zd", this,
+               offset_from_buffer_start);
+        return false;
+      }
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+    return true;
+  }
+
+  // Location that canary value will be written before the guarded region.
+  const char* StartRedZone() const {
+    const uint8_t* buf = reinterpret_cast<const uint8_t*>(this);
+    return reinterpret_cast<const char*>(buf + sizeof(GuardedCopy));
+  }
+
+  // Return the interior embedded buffer.
+  const uint8_t* BufferWithinRedZones() const {
+    const uint8_t* embedded_buf = reinterpret_cast<const uint8_t*>(this) + (kRedZoneSize / 2);
+    return embedded_buf;
+  }
+
+  // Location that canary value will be written after the guarded region.
+  const char* EndRedZone() const {
+    const uint8_t* buf = reinterpret_cast<const uint8_t*>(this);
+    size_t buf_len = LengthIncludingRedZones(original_length_);
+    return reinterpret_cast<const char*>(buf + (buf_len - (kRedZoneSize / 2)));
+  }
+
+  static constexpr size_t kRedZoneSize = 512;
+  static constexpr size_t kEndCanaryLength = kRedZoneSize / 2;
+
+  // Value written before and after the guarded array.
+  static const char* const kCanary;
+
+  static constexpr uint32_t kGuardMagic = 0xffd5aa96;
+
+  const uint32_t magic_;
+  const uLong adler_;
+  const void* const original_ptr_;
+  const size_t original_length_;
 };
-
-/*
- * Create a guarded copy of a primitive array.  Modifications to the copied
- * data are allowed.  Returns a pointer to the copied data.
- */
-static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* isCopy) {
-  ScopedObjectAccess soa(env);
-
-  mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
-  size_t component_size = a->GetClass()->GetComponentSize();
-  size_t byte_count = a->GetLength() * component_size;
-  void* result = GuardedCopy::Create(a->GetRawData(component_size, 0), byte_count, true);
-  if (isCopy != nullptr) {
-    *isCopy = JNI_TRUE;
-  }
-  return result;
-}
-
-/*
- * Perform the array "release" operation, which may or may not copy data
- * back into the managed heap, and may or may not release the underlying storage.
- */
-static void ReleaseGuardedPACopy(JNIEnv* env, jarray java_array, void* dataBuf, int mode) {
-  ScopedObjectAccess soa(env);
-  mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
-
-  GuardedCopy::Check(__FUNCTION__, dataBuf, true);
-
-  if (mode != JNI_ABORT) {
-    size_t len = GuardedCopy::FromData(dataBuf)->original_length;
-    memcpy(a->GetRawData(a->GetClass()->GetComponentSize(), 0), dataBuf, len);
-  }
-  if (mode != JNI_COMMIT) {
-    GuardedCopy::Destroy(dataBuf);
-  }
-}
+const char* const GuardedCopy::kCanary = "JNI BUFFER RED ZONE";
 
 /*
  * ===========================================================================
@@ -1116,668 +1391,1954 @@
 class CheckJNI {
  public:
   static jint GetVersion(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_Default, "E", env);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetVersion(env));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[1] = {{.E = env }};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      result.I = baseEnv(env)->GetVersion(env);
+      if (sc.Check(soa, false, "I", &result)) {
+        return result.I;
+      }
+    }
+    return JNI_ERR;
   }
 
-  static jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf, jsize bufLen) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EuLpz", env, name, loader, buf, bufLen);
-    sc.CheckClassName(name);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->DefineClass(env, name, loader, buf, bufLen));
+  static jint GetJavaVM(JNIEnv *env, JavaVM **vm) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env }, {.p = vm}};
+    if (sc.Check(soa, true, "Ep", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->GetJavaVM(env, vm);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jint RegisterNatives(JNIEnv* env, jclass c, const JNINativeMethod* methods, jint nMethods) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env }, {.c = c}, {.p = methods}, {.I = nMethods}};
+    if (sc.Check(soa, true, "EcpI", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->RegisterNatives(env, c, methods, nMethods);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jint UnregisterNatives(JNIEnv* env, jclass c) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env }, {.c = c}};
+    if (sc.Check(soa, true, "Ec", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->UnregisterNatives(env, c);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) {
+    // Note: we use "Ep" rather than "EL" because this is the one JNI function that it's okay to
+    // pass an invalid reference to.
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env }, {.p = obj}};
+    if (sc.Check(soa, true, "Ep", args)) {
+      JniValueType result;
+      result.w = baseEnv(env)->GetObjectRefType(env, obj);
+      if (sc.Check(soa, false, "w", &result)) {
+        return result.w;
+      }
+    }
+    return JNIInvalidRefType;
+  }
+
+  static jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf,
+                            jsize bufLen) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.u = name}, {.L = loader}, {.p = buf}, {.z = bufLen}};
+    if (sc.Check(soa, true, "EuLpz", args) && sc.CheckClassName(name)) {
+      JniValueType result;
+      result.c = baseEnv(env)->DefineClass(env, name, loader, buf, bufLen);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
   }
 
   static jclass FindClass(JNIEnv* env, const char* name) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Eu", env, name);
-    sc.CheckClassName(name);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->FindClass(env, name));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.u = name}};
+    if (sc.Check(soa, true, "Eu", args) && sc.CheckClassName(name)) {
+      JniValueType result;
+      result.c = baseEnv(env)->FindClass(env, name);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
   }
 
   static jclass GetSuperclass(JNIEnv* env, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->GetSuperclass(env, c));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.c = c}};
+    if (sc.Check(soa, true, "Ec", args)) {
+      JniValueType result;
+      result.c = baseEnv(env)->GetSuperclass(env, c);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
   }
 
   static jboolean IsAssignableFrom(JNIEnv* env, jclass c1, jclass c2) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecc", env, c1, c2);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->IsAssignableFrom(env, c1, c2));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.c = c1}, {.c = c2}};
+    if (sc.Check(soa, true, "Ecc", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->IsAssignableFrom(env, c1, c2);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
   }
 
   static jmethodID FromReflectedMethod(JNIEnv* env, jobject method) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, method);
-    // TODO: check that 'field' is a java.lang.reflect.Method.
-    return CHECK_JNI_EXIT("m", baseEnv(env)->FromReflectedMethod(env, method));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = method}};
+    if (sc.Check(soa, true, "EL", args) && sc.CheckReflectedMethod(soa, method)) {
+      JniValueType result;
+      result.m = baseEnv(env)->FromReflectedMethod(env, method);
+      if (sc.Check(soa, false, "m", &result)) {
+        return result.m;
+      }
+    }
+    return nullptr;
   }
 
   static jfieldID FromReflectedField(JNIEnv* env, jobject field) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, field);
-    // TODO: check that 'field' is a java.lang.reflect.Field.
-    return CHECK_JNI_EXIT("f", baseEnv(env)->FromReflectedField(env, field));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = field}};
+    if (sc.Check(soa, true, "EL", args) && sc.CheckReflectedField(soa, field)) {
+      JniValueType result;
+      result.f = baseEnv(env)->FromReflectedField(env, field);
+      if (sc.Check(soa, false, "f", &result)) {
+        return result.f;
+      }
+    }
+    return nullptr;
   }
 
   static jobject ToReflectedMethod(JNIEnv* env, jclass cls, jmethodID mid, jboolean isStatic) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecmb", env, cls, mid, isStatic);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedMethod(env, cls, mid, isStatic));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env}, {.c = cls}, {.m = mid}, {.b = isStatic}};
+    if (sc.Check(soa, true, "Ecmb", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->ToReflectedMethod(env, cls, mid, isStatic);
+      if (sc.Check(soa, false, "L", &result) && (result.L != nullptr)) {
+        DCHECK(sc.CheckReflectedMethod(soa, result.L));
+        return result.L;
+      }
+    }
+    return nullptr;
   }
 
   static jobject ToReflectedField(JNIEnv* env, jclass cls, jfieldID fid, jboolean isStatic) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecfb", env, cls, fid, isStatic);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedField(env, cls, fid, isStatic));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env}, {.c = cls}, {.f = fid}, {.b = isStatic}};
+    if (sc.Check(soa, true, "Ecfb", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->ToReflectedField(env, cls, fid, isStatic);
+      if (sc.Check(soa, false, "L", &result) && (result.L != nullptr)) {
+        DCHECK(sc.CheckReflectedField(soa, result.L));
+        return result.L;
+      }
+    }
+    return nullptr;
   }
 
   static jint Throw(JNIEnv* env, jthrowable obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    // TODO: check that 'obj' is a java.lang.Throwable.
-    return CHECK_JNI_EXIT("I", baseEnv(env)->Throw(env, obj));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.t = obj}};
+    if (sc.Check(soa, true, "Et", args) && sc.CheckThrowable(soa, obj)) {
+      JniValueType result;
+      result.i = baseEnv(env)->Throw(env, obj);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
   }
 
   static jint ThrowNew(JNIEnv* env, jclass c, const char* message) {
-    CHECK_JNI_ENTRY(kFlag_NullableUtf, "Ecu", env, c, message);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->ThrowNew(env, c, message));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_NullableUtf, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.c = c}, {.u = message}};
+    if (sc.Check(soa, true, "Ecu", args) && sc.CheckThrowableClass(soa, c)) {
+      JniValueType result;
+      result.i = baseEnv(env)->ThrowNew(env, c, message);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
   }
 
   static jthrowable ExceptionOccurred(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->ExceptionOccurred(env));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      result.t = baseEnv(env)->ExceptionOccurred(env);
+      if (sc.Check(soa, false, "t", &result)) {
+        return result.t;
+      }
+    }
+    return nullptr;
   }
 
   static void ExceptionDescribe(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env);
-    baseEnv(env)->ExceptionDescribe(env);
-    CHECK_JNI_EXIT_VOID();
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      baseEnv(env)->ExceptionDescribe(env);
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
   }
 
   static void ExceptionClear(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env);
-    baseEnv(env)->ExceptionClear(env);
-    CHECK_JNI_EXIT_VOID();
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      baseEnv(env)->ExceptionClear(env);
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jboolean ExceptionCheck(JNIEnv* env) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay | kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->ExceptionCheck(env);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
   }
 
   static void FatalError(JNIEnv* env, const char* msg) {
     // The JNI specification doesn't say it's okay to call FatalError with a pending exception,
     // but you're about to abort anyway, and it's quite likely that you have a pending exception,
     // and it's not unimaginable that you don't know that you do. So we allow it.
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay | kFlag_NullableUtf, "Eu", env, msg);
-    baseEnv(env)->FatalError(env, msg);
-    CHECK_JNI_EXIT_VOID();
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay | kFlag_NullableUtf, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.u = msg}};
+    if (sc.Check(soa, true, "Eu", args)) {
+      JniValueType result;
+      baseEnv(env)->FatalError(env, msg);
+      // Unreachable.
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
   }
 
   static jint PushLocalFrame(JNIEnv* env, jint capacity) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EI", env, capacity);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->PushLocalFrame(env, capacity));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.I = capacity}};
+    if (sc.Check(soa, true, "EI", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->PushLocalFrame(env, capacity);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
   }
 
   static jobject PopLocalFrame(JNIEnv* env, jobject res) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, res);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->PopLocalFrame(env, res));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = res}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->PopLocalFrame(env, res);
+      sc.Check(soa, false, "L", &result);
+      return result.L;
+    }
+    return nullptr;
   }
 
   static jobject NewGlobalRef(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewGlobalRef(env, obj));
+    return NewRef(__FUNCTION__, env, obj, kGlobal);
   }
 
-  static jobject NewLocalRef(JNIEnv* env, jobject ref) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, ref);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewLocalRef(env, ref));
-  }
-
-  static void DeleteGlobalRef(JNIEnv* env, jobject globalRef) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, globalRef);
-    if (globalRef != nullptr && GetIndirectRefKind(globalRef) != kGlobal) {
-      JniAbortF(__FUNCTION__, "DeleteGlobalRef on %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(globalRef)).c_str(), globalRef);
-    } else {
-      baseEnv(env)->DeleteGlobalRef(env, globalRef);
-      CHECK_JNI_EXIT_VOID();
-    }
-  }
-
-  static void DeleteWeakGlobalRef(JNIEnv* env, jweak weakGlobalRef) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, weakGlobalRef);
-    if (weakGlobalRef != nullptr && GetIndirectRefKind(weakGlobalRef) != kWeakGlobal) {
-      JniAbortF(__FUNCTION__, "DeleteWeakGlobalRef on %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(weakGlobalRef)).c_str(), weakGlobalRef);
-    } else {
-      baseEnv(env)->DeleteWeakGlobalRef(env, weakGlobalRef);
-      CHECK_JNI_EXIT_VOID();
-    }
-  }
-
-  static void DeleteLocalRef(JNIEnv* env, jobject localRef) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, localRef);
-    if (localRef != nullptr && GetIndirectRefKind(localRef) != kLocal && !IsHandleScopeLocalRef(env, localRef)) {
-      JniAbortF(__FUNCTION__, "DeleteLocalRef on %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(localRef)).c_str(), localRef);
-    } else {
-      baseEnv(env)->DeleteLocalRef(env, localRef);
-      CHECK_JNI_EXIT_VOID();
-    }
-  }
-
-  static jint EnsureLocalCapacity(JNIEnv *env, jint capacity) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EI", env, capacity);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->EnsureLocalCapacity(env, capacity));
-  }
-
-  static jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2) {
-    CHECK_JNI_ENTRY(kFlag_Default, "ELL", env, ref1, ref2);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->IsSameObject(env, ref1, ref2));
-  }
-
-  static jobject AllocObject(JNIEnv* env, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->AllocObject(env, c));
-  }
-
-  static jobject NewObject(JNIEnv* env, jclass c, jmethodID mid, ...) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid);
-    va_list args;
-    va_start(args, mid);
-    jobject result = baseEnv(env)->NewObjectV(env, c, mid, args);
-    va_end(args);
-    return CHECK_JNI_EXIT("L", result);
-  }
-
-  static jobject NewObjectV(JNIEnv* env, jclass c, jmethodID mid, va_list args) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectV(env, c, mid, args));
-  }
-
-  static jobject NewObjectA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectA(env, c, mid, args));
-  }
-
-  static jclass GetObjectClass(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->GetObjectClass(env, obj));
-  }
-
-  static jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "ELc", env, obj, c);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->IsInstanceOf(env, obj, c));
-  }
-
-  static jmethodID GetMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("m", baseEnv(env)->GetMethodID(env, c, name, sig));
-  }
-
-  static jfieldID GetFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("f", baseEnv(env)->GetFieldID(env, c, name, sig));
-  }
-
-  static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("m", baseEnv(env)->GetStaticMethodID(env, c, name, sig));
-  }
-
-  static jfieldID GetStaticFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("f", baseEnv(env)->GetStaticFieldID(env, c, name, sig));
-  }
-
-#define FIELD_ACCESSORS(_ctype, _jname, _jvalue_type, _type) \
-    static _ctype GetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecf", env, c, fid); \
-        sc.CheckStaticFieldID(c, fid); \
-        return CHECK_JNI_EXIT(_type, baseEnv(env)->GetStatic##_jname##Field(env, c, fid)); \
-    } \
-    static _ctype Get##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELf", env, obj, fid); \
-        sc.CheckInstanceFieldID(obj, fid); \
-        return CHECK_JNI_EXIT(_type, baseEnv(env)->Get##_jname##Field(env, obj, fid)); \
-    } \
-    static void SetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid, _ctype value) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecf" _type, env, c, fid, value); \
-        sc.CheckStaticFieldID(c, fid); \
-        /* "value" arg only used when type == ref */ \
-        jvalue java_type_value; \
-        java_type_value._jvalue_type = value; \
-        sc.CheckFieldType(java_type_value, fid, _type[0], true); \
-        baseEnv(env)->SetStatic##_jname##Field(env, c, fid, value); \
-        CHECK_JNI_EXIT_VOID(); \
-    } \
-    static void Set##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid, _ctype value) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELf" _type, env, obj, fid, value); \
-        sc.CheckInstanceFieldID(obj, fid); \
-        /* "value" arg only used when type == ref */ \
-        jvalue java_type_value; \
-        java_type_value._jvalue_type = value; \
-        sc.CheckFieldType(java_type_value, fid, _type[0], false); \
-        baseEnv(env)->Set##_jname##Field(env, obj, fid, value); \
-        CHECK_JNI_EXIT_VOID(); \
-    }
-
-FIELD_ACCESSORS(jobject, Object, l, "L");
-FIELD_ACCESSORS(jboolean, Boolean, z, "Z");
-FIELD_ACCESSORS(jbyte, Byte, b, "B");
-FIELD_ACCESSORS(jchar, Char, c, "C");
-FIELD_ACCESSORS(jshort, Short, s, "S");
-FIELD_ACCESSORS(jint, Int, i, "I");
-FIELD_ACCESSORS(jlong, Long, j, "J");
-FIELD_ACCESSORS(jfloat, Float, f, "F");
-FIELD_ACCESSORS(jdouble, Double, d, "D");
-
-#define CALL(_ctype, _jname, _retdecl, _retasgn, _retok, _retsig) \
-    /* Virtual... */ \
-    static _ctype Call##_jname##Method(JNIEnv* env, jobject obj, \
-        jmethodID mid, ...) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        va_list args; \
-        va_start(args, mid); \
-        _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \
-        va_end(args); \
-        _retok; \
-    } \
-    static _ctype Call##_jname##MethodV(JNIEnv* env, jobject obj, \
-        jmethodID mid, va_list args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \
-        _retok; \
-    } \
-    static _ctype Call##_jname##MethodA(JNIEnv* env, jobject obj, \
-        jmethodID mid, jvalue* args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->Call##_jname##MethodA(env, obj, mid, args)); \
-        _retok; \
-    } \
-    /* Non-virtual... */ \
-    static _ctype CallNonvirtual##_jname##Method(JNIEnv* env, \
-        jobject obj, jclass c, jmethodID mid, ...) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        va_list args; \
-        va_start(args, mid); \
-        _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \
-        va_end(args); \
-        _retok; \
-    } \
-    static _ctype CallNonvirtual##_jname##MethodV(JNIEnv* env, \
-        jobject obj, jclass c, jmethodID mid, va_list args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \
-        _retok; \
-    } \
-    static _ctype CallNonvirtual##_jname##MethodA(JNIEnv* env, \
-        jobject obj, jclass c, jmethodID mid, jvalue* args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodA(env, obj, c, mid, args)); \
-        _retok; \
-    } \
-    /* Static... */ \
-    static _ctype CallStatic##_jname##Method(JNIEnv* env, jclass c, jmethodID mid, ...) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, true); \
-        sc.CheckStaticMethod(c, mid); \
-        _retdecl; \
-        va_list args; \
-        va_start(args, mid); \
-        _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \
-        va_end(args); \
-        _retok; \
-    } \
-    static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass c, jmethodID mid, va_list args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, true); \
-        sc.CheckStaticMethod(c, mid); \
-        _retdecl; \
-         _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \
-        _retok; \
-    } \
-    static _ctype CallStatic##_jname##MethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, true); \
-        sc.CheckStaticMethod(c, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->CallStatic##_jname##MethodA(env, c, mid, args)); \
-        _retok; \
-    }
-
-#define NON_VOID_RETURN(_retsig, _ctype) return CHECK_JNI_EXIT(_retsig, (_ctype) result)
-#define VOID_RETURN CHECK_JNI_EXIT_VOID()
-
-CALL(jobject, Object, mirror::Object* result, result = reinterpret_cast<mirror::Object*>, NON_VOID_RETURN("L", jobject), "L");
-CALL(jboolean, Boolean, jboolean result, result =, NON_VOID_RETURN("Z", jboolean), "Z");
-CALL(jbyte, Byte, jbyte result, result =, NON_VOID_RETURN("B", jbyte), "B");
-CALL(jchar, Char, jchar result, result =, NON_VOID_RETURN("C", jchar), "C");
-CALL(jshort, Short, jshort result, result =, NON_VOID_RETURN("S", jshort), "S");
-CALL(jint, Int, jint result, result =, NON_VOID_RETURN("I", jint), "I");
-CALL(jlong, Long, jlong result, result =, NON_VOID_RETURN("J", jlong), "J");
-CALL(jfloat, Float, jfloat result, result =, NON_VOID_RETURN("F", jfloat), "F");
-CALL(jdouble, Double, jdouble result, result =, NON_VOID_RETURN("D", jdouble), "D");
-CALL(void, Void, , , VOID_RETURN, "V");
-
-  static jstring NewString(JNIEnv* env, const jchar* unicodeChars, jsize len) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Epz", env, unicodeChars, len);
-    return CHECK_JNI_EXIT("s", baseEnv(env)->NewString(env, unicodeChars, len));
-  }
-
-  static jsize GetStringLength(JNIEnv* env, jstring string) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringLength(env, string));
-  }
-
-  static const jchar* GetStringChars(JNIEnv* env, jstring java_string, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, java_string, isCopy);
-    const jchar* result = baseEnv(env)->GetStringChars(env, java_string, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      mirror::String* s = sc.soa().Decode<mirror::String*>(java_string);
-      int byteCount = s->GetLength() * 2;
-      result = (const jchar*) GuardedCopy::Create(result, byteCount, false);
-      if (isCopy != nullptr) {
-        *isCopy = JNI_TRUE;
-      }
-    }
-    return CHECK_JNI_EXIT("p", result);
-  }
-
-  static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Esp", env, string, chars);
-    sc.CheckNonNull(chars);
-    if (sc.ForceCopy()) {
-      GuardedCopy::Check(__FUNCTION__, chars, false);
-      chars = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<jchar*>(chars)));
-    }
-    baseEnv(env)->ReleaseStringChars(env, string, chars);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static jstring NewStringUTF(JNIEnv* env, const char* bytes) {
-    CHECK_JNI_ENTRY(kFlag_NullableUtf, "Eu", env, bytes);  // TODO: show pointer and truncate string.
-    return CHECK_JNI_EXIT("s", baseEnv(env)->NewStringUTF(env, bytes));
-  }
-
-  static jsize GetStringUTFLength(JNIEnv* env, jstring string) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringUTFLength(env, string));
-  }
-
-  static const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, string, isCopy);
-    const char* result = baseEnv(env)->GetStringUTFChars(env, string, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      result = (const char*) GuardedCopy::Create(result, strlen(result) + 1, false);
-      if (isCopy != nullptr) {
-        *isCopy = JNI_TRUE;
-      }
-    }
-    return CHECK_JNI_EXIT("u", result);  // TODO: show pointer and truncate string.
-  }
-
-  static void ReleaseStringUTFChars(JNIEnv* env, jstring string, const char* utf) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay | kFlag_Release, "Esu", env, string, utf);  // TODO: show pointer and truncate string.
-    if (sc.ForceCopy()) {
-      GuardedCopy::Check(__FUNCTION__, utf, false);
-      utf = reinterpret_cast<const char*>(GuardedCopy::Destroy(const_cast<char*>(utf)));
-    }
-    baseEnv(env)->ReleaseStringUTFChars(env, string, utf);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static jsize GetArrayLength(JNIEnv* env, jarray array) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Ea", env, array);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetArrayLength(env, array));
-  }
-
-  static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass elementClass, jobject initialElement) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EzcL", env, length, elementClass, initialElement);
-    return CHECK_JNI_EXIT("a", baseEnv(env)->NewObjectArray(env, length, elementClass, initialElement));
-  }
-
-  static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EaI", env, array, index);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->GetObjectArrayElement(env, array, index));
-  }
-
-  static void SetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index, jobject value) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EaIL", env, array, index, value);
-    baseEnv(env)->SetObjectArrayElement(env, array, index, value);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-#define NEW_PRIMITIVE_ARRAY(_artype, _jname) \
-    static _artype New##_jname##Array(JNIEnv* env, jsize length) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ez", env, length); \
-        return CHECK_JNI_EXIT("a", baseEnv(env)->New##_jname##Array(env, length)); \
-    }
-NEW_PRIMITIVE_ARRAY(jbooleanArray, Boolean);
-NEW_PRIMITIVE_ARRAY(jbyteArray, Byte);
-NEW_PRIMITIVE_ARRAY(jcharArray, Char);
-NEW_PRIMITIVE_ARRAY(jshortArray, Short);
-NEW_PRIMITIVE_ARRAY(jintArray, Int);
-NEW_PRIMITIVE_ARRAY(jlongArray, Long);
-NEW_PRIMITIVE_ARRAY(jfloatArray, Float);
-NEW_PRIMITIVE_ARRAY(jdoubleArray, Double);
-
-struct ForceCopyGetChecker {
- public:
-  ForceCopyGetChecker(ScopedCheck& sc, jboolean* isCopy) {
-    force_copy = sc.ForceCopy();
-    no_copy = 0;
-    if (force_copy && isCopy != nullptr) {
-      // Capture this before the base call tramples on it.
-      no_copy = *reinterpret_cast<uint32_t*>(isCopy);
-    }
-  }
-
-  template<typename ResultT>
-  ResultT Check(JNIEnv* env, jarray array, jboolean* isCopy, ResultT result) {
-    if (force_copy && result != nullptr) {
-      result = reinterpret_cast<ResultT>(CreateGuardedPACopy(env, array, isCopy));
-    }
-    return result;
-  }
-
-  uint32_t no_copy;
-  bool force_copy;
-};
-
-#define GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \
-  static _ctype* Get##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, jboolean* isCopy) { \
-    CHECK_JNI_ENTRY(kFlag_Default, "Eap", env, array, isCopy); \
-    _ctype* result = ForceCopyGetChecker(sc, isCopy).Check(env, array, isCopy, baseEnv(env)->Get##_jname##ArrayElements(env, array, isCopy)); \
-    return CHECK_JNI_EXIT("p", result); \
-  }
-
-#define RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \
-  static void Release##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, _ctype* elems, jint mode) { \
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Eapr", env, array, elems, mode); \
-    sc.CheckNonNull(elems); \
-    if (sc.ForceCopy()) { \
-      ReleaseGuardedPACopy(env, array, elems, mode); \
-    } \
-    baseEnv(env)->Release##_jname##ArrayElements(env, array, elems, mode); \
-    CHECK_JNI_EXIT_VOID(); \
-  }
-
-#define GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \
-    static void Get##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, _ctype* buf) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \
-        baseEnv(env)->Get##_jname##ArrayRegion(env, array, start, len, buf); \
-        CHECK_JNI_EXIT_VOID(); \
-    }
-
-#define SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \
-    static void Set##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, const _ctype* buf) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \
-        baseEnv(env)->Set##_jname##ArrayRegion(env, array, start, len, buf); \
-        CHECK_JNI_EXIT_VOID(); \
-    }
-
-#define PRIMITIVE_ARRAY_FUNCTIONS(_ctype, _jname, _typechar) \
-    GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \
-    RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \
-    GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); \
-    SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname);
-
-// TODO: verify primitive array type matches call type.
-PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, 'Z');
-PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, 'B');
-PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, 'C');
-PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, 'S');
-PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, 'I');
-PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, 'J');
-PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, 'F');
-PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, 'D');
-
-  static jint RegisterNatives(JNIEnv* env, jclass c, const JNINativeMethod* methods, jint nMethods) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EcpI", env, c, methods, nMethods);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->RegisterNatives(env, c, methods, nMethods));
-  }
-
-  static jint UnregisterNatives(JNIEnv* env, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->UnregisterNatives(env, c));
-  }
-
-  static jint MonitorEnter(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    if (!sc.CheckInstance(ScopedCheck::kObject, obj)) {
-      return JNI_ERR;  // Only for jni_internal_test. Real code will have aborted already.
-    }
-    return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorEnter(env, obj));
-  }
-
-  static jint MonitorExit(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, obj);
-    if (!sc.CheckInstance(ScopedCheck::kObject, obj)) {
-      return JNI_ERR;  // Only for jni_internal_test. Real code will have aborted already.
-    }
-    return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorExit(env, obj));
-  }
-
-  static jint GetJavaVM(JNIEnv *env, JavaVM **vm) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, vm);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetJavaVM(env, vm));
-  }
-
-  static void GetStringRegion(JNIEnv* env, jstring str, jsize start, jsize len, jchar* buf) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf);
-    baseEnv(env)->GetStringRegion(env, str, start, len, buf);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static void GetStringUTFRegion(JNIEnv* env, jstring str, jsize start, jsize len, char* buf) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf);
-    baseEnv(env)->GetStringUTFRegion(env, str, start, len, buf);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritGet, "Eap", env, array, isCopy);
-    void* result = baseEnv(env)->GetPrimitiveArrayCritical(env, array, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      result = CreateGuardedPACopy(env, array, isCopy);
-    }
-    return CHECK_JNI_EXIT("p", result);
-  }
-
-  static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) {
-    CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Eapr", env, array, carray, mode);
-    sc.CheckNonNull(carray);
-    if (sc.ForceCopy()) {
-      ReleaseGuardedPACopy(env, array, carray, mode);
-    }
-    baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static const jchar* GetStringCritical(JNIEnv* env, jstring java_string, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritGet, "Esp", env, java_string, isCopy);
-    const jchar* result = baseEnv(env)->GetStringCritical(env, java_string, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      mirror::String* s = sc.soa().Decode<mirror::String*>(java_string);
-      int byteCount = s->GetLength() * 2;
-      result = (const jchar*) GuardedCopy::Create(result, byteCount, false);
-      if (isCopy != nullptr) {
-        *isCopy = JNI_TRUE;
-      }
-    }
-    return CHECK_JNI_EXIT("p", result);
-  }
-
-  static void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* carray) {
-    CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Esp", env, string, carray);
-    sc.CheckNonNull(carray);
-    if (sc.ForceCopy()) {
-      GuardedCopy::Check(__FUNCTION__, carray, false);
-      carray = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<jchar*>(carray)));
-    }
-    baseEnv(env)->ReleaseStringCritical(env, string, carray);
-    CHECK_JNI_EXIT_VOID();
+  static jobject NewLocalRef(JNIEnv* env, jobject obj) {
+    return NewRef(__FUNCTION__, env, obj, kLocal);
   }
 
   static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewWeakGlobalRef(env, obj));
+    return NewRef(__FUNCTION__, env, obj, kWeakGlobal);
   }
 
-  static jboolean ExceptionCheck(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay | kFlag_ExcepOkay, "E", env);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->ExceptionCheck(env));
+  static void DeleteGlobalRef(JNIEnv* env, jobject obj) {
+    DeleteRef(__FUNCTION__, env, obj, kGlobal);
   }
 
-  static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) {
-    // Note: we use "Ep" rather than "EL" because this is the one JNI function
-    // that it's okay to pass an invalid reference to.
-    CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, obj);
-    // TODO: proper decoding of jobjectRefType!
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetObjectRefType(env, obj));
+  static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {
+    DeleteRef(__FUNCTION__, env, obj, kWeakGlobal);
+  }
+
+  static void DeleteLocalRef(JNIEnv* env, jobject obj) {
+    DeleteRef(__FUNCTION__, env, obj, kLocal);
+  }
+
+  static jint EnsureLocalCapacity(JNIEnv *env, jint capacity) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.I = capacity}};
+    if (sc.Check(soa, true, "EI", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->EnsureLocalCapacity(env, capacity);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.L = ref1}, {.L = ref2}};
+    if (sc.Check(soa, true, "ELL", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->IsSameObject(env, ref1, ref2);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
+  }
+
+  static jobject AllocObject(JNIEnv* env, jclass c) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.c = c}};
+    if (sc.Check(soa, true, "Ec", args) && sc.CheckInstantiableNonArray(soa, c)) {
+      JniValueType result;
+      result.L = baseEnv(env)->AllocObject(env, c);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static jobject NewObjectV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}};
+    if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) &&
+        sc.CheckConstructor(soa, mid)) {
+      JniValueType result;
+      result.L = baseEnv(env)->NewObjectV(env, c, mid, vargs);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static jobject NewObject(JNIEnv* env, jclass c, jmethodID mid, ...) {
+    va_list args;
+    va_start(args, mid);
+    jobject result = NewObjectV(env, c, mid, args);
+    va_end(args);
+    return result;
+  }
+
+  static jobject NewObjectA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}};
+    if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) &&
+        sc.CheckConstructor(soa, mid)) {
+      JniValueType result;
+      result.L = baseEnv(env)->NewObjectA(env, c, mid, vargs);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static jclass GetObjectClass(JNIEnv* env, jobject obj) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.c = baseEnv(env)->GetObjectClass(env, obj);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
+  }
+
+  static jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass c) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.L = obj}, {.c = c}};
+    if (sc.Check(soa, true, "ELc", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->IsInstanceOf(env, obj, c);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
+  }
+
+  static jmethodID GetMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetMethodIDInternal(__FUNCTION__, env, c, name, sig, false);
+  }
+
+  static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetMethodIDInternal(__FUNCTION__, env, c, name, sig, true);
+  }
+
+  static jfieldID GetFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetFieldIDInternal(__FUNCTION__, env, c, name, sig, false);
+  }
+
+  static jfieldID GetStaticFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetFieldIDInternal(__FUNCTION__, env, c, name, sig, true);
+  }
+
+#define FIELD_ACCESSORS(jtype, name, ptype, shorty) \
+  static jtype GetStatic##name##Field(JNIEnv* env, jclass c, jfieldID fid) { \
+    return GetField(__FUNCTION__, env, c, fid, true, ptype).shorty; \
+  } \
+  \
+  static jtype Get##name##Field(JNIEnv* env, jobject obj, jfieldID fid) { \
+    return GetField(__FUNCTION__, env, obj, fid, false, ptype).shorty; \
+  } \
+  \
+  static void SetStatic##name##Field(JNIEnv* env, jclass c, jfieldID fid, jtype v) { \
+    JniValueType value; \
+    value.shorty = v; \
+    SetField(__FUNCTION__, env, c, fid, true, ptype, value); \
+  } \
+  \
+  static void Set##name##Field(JNIEnv* env, jobject obj, jfieldID fid, jtype v) { \
+    JniValueType value; \
+    value.shorty = v; \
+    SetField(__FUNCTION__, env, obj, fid, false, ptype, value); \
+  }
+
+  FIELD_ACCESSORS(jobject, Object, Primitive::kPrimNot, L)
+  FIELD_ACCESSORS(jboolean, Boolean, Primitive::kPrimBoolean, Z)
+  FIELD_ACCESSORS(jbyte, Byte, Primitive::kPrimByte, B)
+  FIELD_ACCESSORS(jchar, Char, Primitive::kPrimChar, C)
+  FIELD_ACCESSORS(jshort, Short, Primitive::kPrimShort, S)
+  FIELD_ACCESSORS(jint, Int, Primitive::kPrimInt, I)
+  FIELD_ACCESSORS(jlong, Long, Primitive::kPrimLong, J)
+  FIELD_ACCESSORS(jfloat, Float, Primitive::kPrimFloat, F)
+  FIELD_ACCESSORS(jdouble, Double, Primitive::kPrimDouble, D)
+#undef FIELD_ACCESSORS
+
+  static void CallVoidMethodA(JNIEnv* env, jobject obj, jmethodID mid, jvalue* vargs) {
+    CallMethodA(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual);
+  }
+
+  static void CallNonvirtualVoidMethodA(JNIEnv* env, jobject obj, jclass c, jmethodID mid,
+                                        jvalue* vargs) {
+    CallMethodA(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect);
+  }
+
+  static void CallStaticVoidMethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) {
+    CallMethodA(__FUNCTION__, env, c, nullptr, mid, vargs, Primitive::kPrimVoid, kStatic);
+  }
+
+  static void CallVoidMethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list vargs) {
+    CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual);
+  }
+
+  static void CallNonvirtualVoidMethodV(JNIEnv* env, jobject obj, jclass c, jmethodID mid,
+                                        va_list vargs) {
+    CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect);
+  }
+
+  static void CallStaticVoidMethodV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) {
+    CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, Primitive::kPrimVoid, kStatic);
+  }
+
+  static void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID mid, ...) {
+    va_list vargs;
+    va_start(vargs, mid);
+    CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual);
+    va_end(vargs);
+  }
+
+  static void CallNonvirtualVoidMethod(JNIEnv* env, jobject obj, jclass c, jmethodID mid, ...) {
+    va_list vargs;
+    va_start(vargs, mid);
+    CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect);
+    va_end(vargs);
+  }
+
+  static void CallStaticVoidMethod(JNIEnv* env, jclass c, jmethodID mid, ...) {
+    va_list vargs;
+    va_start(vargs, mid);
+    CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, Primitive::kPrimVoid, kStatic);
+    va_end(vargs);
+  }
+
+#define CALL(rtype, name, ptype, shorty) \
+  static rtype Call##name##MethodA(JNIEnv* env, jobject obj, jmethodID mid, jvalue* vargs) { \
+    return CallMethodA(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \
+  } \
+  \
+  static rtype CallNonvirtual##name##MethodA(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \
+                                             jvalue* vargs) { \
+    return CallMethodA(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \
+  } \
+  \
+  static rtype CallStatic##name##MethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) { \
+    return CallMethodA(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \
+  } \
+  \
+  static rtype Call##name##MethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list vargs) { \
+    return CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \
+  } \
+  \
+  static rtype CallNonvirtual##name##MethodV(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \
+                                             va_list vargs) { \
+    return CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \
+  } \
+  \
+  static rtype CallStatic##name##MethodV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) { \
+    return CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \
+  } \
+  \
+  static rtype Call##name##Method(JNIEnv* env, jobject obj, jmethodID mid, ...) { \
+    va_list vargs; \
+    va_start(vargs, mid); \
+    rtype result = \
+        CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \
+    va_end(vargs); \
+    return result; \
+  } \
+  \
+  static rtype CallNonvirtual##name##Method(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \
+                                            ...) { \
+    va_list vargs; \
+    va_start(vargs, mid); \
+    rtype result = \
+        CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \
+    va_end(vargs); \
+    return result; \
+  } \
+  \
+  static rtype CallStatic##name##Method(JNIEnv* env, jclass c, jmethodID mid, ...) { \
+    va_list vargs; \
+    va_start(vargs, mid); \
+    rtype result = \
+        CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \
+    va_end(vargs); \
+    return result; \
+  }
+
+  CALL(jobject, Object, Primitive::kPrimNot, L)
+  CALL(jboolean, Boolean, Primitive::kPrimBoolean, Z)
+  CALL(jbyte, Byte, Primitive::kPrimByte, B)
+  CALL(jchar, Char, Primitive::kPrimChar, C)
+  CALL(jshort, Short, Primitive::kPrimShort, S)
+  CALL(jint, Int, Primitive::kPrimInt, I)
+  CALL(jlong, Long, Primitive::kPrimLong, J)
+  CALL(jfloat, Float, Primitive::kPrimFloat, F)
+  CALL(jdouble, Double, Primitive::kPrimDouble, D)
+#undef CALL
+
+  static jstring NewString(JNIEnv* env, const jchar* unicode_chars, jsize len) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.p = unicode_chars}, {.z = len}};
+    if (sc.Check(soa, true, "Epz", args)) {
+      JniValueType result;
+      result.s = baseEnv(env)->NewString(env, unicode_chars, len);
+      if (sc.Check(soa, false, "s", &result)) {
+        return result.s;
+      }
+    }
+    return nullptr;
+  }
+
+  static jstring NewStringUTF(JNIEnv* env, const char* chars) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_NullableUtf, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.u = chars}};
+    if (sc.Check(soa, true, "Eu", args)) {
+      JniValueType result;
+      // TODO: stale? show pointer and truncate string.
+      result.s = baseEnv(env)->NewStringUTF(env, chars);
+      if (sc.Check(soa, false, "s", &result)) {
+        return result.s;
+      }
+    }
+    return nullptr;
+  }
+
+  static jsize GetStringLength(JNIEnv* env, jstring string) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.s = string}};
+    if (sc.Check(soa, true, "Es", args)) {
+      JniValueType result;
+      result.z = baseEnv(env)->GetStringLength(env, string);
+      if (sc.Check(soa, false, "z", &result)) {
+        return result.z;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jsize GetStringUTFLength(JNIEnv* env, jstring string) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.s = string}};
+    if (sc.Check(soa, true, "Es", args)) {
+      JniValueType result;
+      result.z = baseEnv(env)->GetStringUTFLength(env, string);
+      if (sc.Check(soa, false, "z", &result)) {
+        return result.z;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static const jchar* GetStringChars(JNIEnv* env, jstring string, jboolean* is_copy) {
+    return reinterpret_cast<const jchar*>(GetStringCharsInternal(__FUNCTION__, env, string,
+                                                                 is_copy, false, false));
+  }
+
+  static const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* is_copy) {
+    return reinterpret_cast<const char*>(GetStringCharsInternal(__FUNCTION__, env, string,
+                                                                is_copy, true, false));
+  }
+
+  static const jchar* GetStringCritical(JNIEnv* env, jstring string, jboolean* is_copy) {
+    return reinterpret_cast<const jchar*>(GetStringCharsInternal(__FUNCTION__, env, string,
+                                                                 is_copy, false, true));
+  }
+
+  static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars) {
+    ReleaseStringCharsInternal(__FUNCTION__, env, string, chars, false, false);
+  }
+
+  static void ReleaseStringUTFChars(JNIEnv* env, jstring string, const char* utf) {
+    ReleaseStringCharsInternal(__FUNCTION__, env, string, utf, true, false);
+  }
+
+  static void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* chars) {
+    ReleaseStringCharsInternal(__FUNCTION__, env, string, chars, false, true);
+  }
+
+  static void GetStringRegion(JNIEnv* env, jstring string, jsize start, jsize len, jchar* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.s = string}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EsIIp", args)) {
+      baseEnv(env)->GetStringRegion(env, string, start, len, buf);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static void GetStringUTFRegion(JNIEnv* env, jstring string, jsize start, jsize len, char* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.s = string}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EsIIp", args)) {
+      baseEnv(env)->GetStringUTFRegion(env, string, start, len, buf);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jsize GetArrayLength(JNIEnv* env, jarray array) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.a = array}};
+    if (sc.Check(soa, true, "Ea", args)) {
+      JniValueType result;
+      result.z = baseEnv(env)->GetArrayLength(env, array);
+      if (sc.Check(soa, false, "z", &result)) {
+        return result.z;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass element_class,
+                                     jobject initial_element) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] =
+        {{.E = env}, {.z = length}, {.c = element_class}, {.L = initial_element}};
+    if (sc.Check(soa, true, "EzcL", args)) {
+      JniValueType result;
+      // Note: assignability tests of initial_element are done in the base implementation.
+      result.a = baseEnv(env)->NewObjectArray(env, length, element_class, initial_element);
+      if (sc.Check(soa, false, "a", &result)) {
+        return down_cast<jobjectArray>(result.a);
+      }
+    }
+    return nullptr;
+  }
+
+  static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.a = array}, {.z = index}};
+    if (sc.Check(soa, true, "Eaz", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->GetObjectArrayElement(env, array, index);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static void SetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index, jobject value) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env}, {.a = array}, {.z = index}, {.L = value}};
+    // Note: the index arguments is checked as 'I' rather than 'z' as invalid indices result in
+    // ArrayIndexOutOfBoundsExceptions in the base implementation. Similarly invalid stores result
+    // in ArrayStoreExceptions.
+    if (sc.Check(soa, true, "EaIL", args)) {
+      baseEnv(env)->SetObjectArrayElement(env, array, index, value);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jbooleanArray NewBooleanArray(JNIEnv* env, jsize length) {
+    return down_cast<jbooleanArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                      Primitive::kPrimBoolean));
+  }
+
+  static jbyteArray NewByteArray(JNIEnv* env, jsize length) {
+    return down_cast<jbyteArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                   Primitive::kPrimByte));
+  }
+
+  static jcharArray NewCharArray(JNIEnv* env, jsize length) {
+    return down_cast<jcharArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                   Primitive::kPrimChar));
+  }
+
+  static jshortArray NewShortArray(JNIEnv* env, jsize length) {
+    return down_cast<jshortArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                    Primitive::kPrimShort));
+  }
+
+  static jintArray NewIntArray(JNIEnv* env, jsize length) {
+    return down_cast<jintArray>(NewPrimitiveArray(__FUNCTION__, env, length, Primitive::kPrimInt));
+  }
+
+  static jlongArray NewLongArray(JNIEnv* env, jsize length) {
+    return down_cast<jlongArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                   Primitive::kPrimLong));
+  }
+
+  static jfloatArray NewFloatArray(JNIEnv* env, jsize length) {
+    return down_cast<jfloatArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                    Primitive::kPrimFloat));
+  }
+
+  static jdoubleArray NewDoubleArray(JNIEnv* env, jsize length) {
+    return down_cast<jdoubleArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                     Primitive::kPrimDouble));
+  }
+
+#define PRIMITIVE_ARRAY_FUNCTIONS(ctype, name, ptype) \
+  static ctype* Get##name##ArrayElements(JNIEnv* env, ctype##Array array, jboolean* is_copy) { \
+    return reinterpret_cast<ctype*>( \
+        GetPrimitiveArrayElements(__FUNCTION__, ptype, env, array, is_copy)); \
+  } \
+  \
+  static void Release##name##ArrayElements(JNIEnv* env, ctype##Array array, ctype* elems, \
+                                           jint mode) { \
+    ReleasePrimitiveArrayElements(__FUNCTION__, ptype, env, array, elems, mode); \
+  } \
+  \
+  static void Get##name##ArrayRegion(JNIEnv* env, ctype##Array array, jsize start, jsize len, \
+                                     ctype* buf) { \
+    GetPrimitiveArrayRegion(__FUNCTION__, ptype, env, array, start, len, buf); \
+  } \
+  \
+  static void Set##name##ArrayRegion(JNIEnv* env, ctype##Array array, jsize start, jsize len, \
+                                     const ctype* buf) { \
+    SetPrimitiveArrayRegion(__FUNCTION__, ptype, env, array, start, len, buf); \
+  }
+
+  PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, Primitive::kPrimBoolean)
+  PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, Primitive::kPrimByte)
+  PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, Primitive::kPrimChar)
+  PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, Primitive::kPrimShort)
+  PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, Primitive::kPrimInt)
+  PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, Primitive::kPrimLong)
+  PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, Primitive::kPrimFloat)
+  PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, Primitive::kPrimDouble)
+#undef PRIMITIVE_ARRAY_FUNCTIONS
+
+  static jint MonitorEnter(JNIEnv* env, jobject obj) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->MonitorEnter(env, obj);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jint MonitorExit(JNIEnv* env, jobject obj) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->MonitorExit(env, obj);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* is_copy) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritGet, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.a = array}, {.p = is_copy}};
+    if (sc.Check(soa, true, "Eap", args)) {
+      JniValueType result;
+      result.p = baseEnv(env)->GetPrimitiveArrayCritical(env, array, is_copy);
+      if (result.p != nullptr && soa.ForceCopy()) {
+        result.p = GuardedCopy::CreateGuardedPACopy(env, array, is_copy);
+      }
+      if (sc.Check(soa, false, "p", &result)) {
+        return const_cast<void*>(result.p);
+      }
+    }
+    return nullptr;
+  }
+
+  static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritRelease | kFlag_ExcepOkay, __FUNCTION__);
+    sc.CheckNonNull(carray);
+    JniValueType args[4] = {{.E = env}, {.a = array}, {.p = carray}, {.r = mode}};
+    if (sc.Check(soa, true, "Eapr", args)) {
+      if (soa.ForceCopy()) {
+        GuardedCopy::ReleaseGuardedPACopy(__FUNCTION__, env, array, carray, mode);
+      }
+      baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
   }
 
   static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EpJ", env, address, capacity);
-    if (address == nullptr) {
-      JniAbortF(__FUNCTION__, "non-nullable address is NULL");
-      return nullptr;
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.p = address}, {.J = capacity}};
+    if (sc.Check(soa, true, "EpJ", args)) {
+      JniValueType result;
+      // Note: the validity of address and capacity are checked in the base implementation.
+      result.L = baseEnv(env)->NewDirectByteBuffer(env, address, capacity);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
     }
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewDirectByteBuffer(env, address, capacity));
+    return nullptr;
   }
 
   static void* GetDirectBufferAddress(JNIEnv* env, jobject buf) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf);
-    // TODO: check that 'buf' is a java.nio.Buffer.
-    return CHECK_JNI_EXIT("p", baseEnv(env)->GetDirectBufferAddress(env, buf));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = buf}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      // Note: this is implemented in the base environment by a GetLongField which will sanity
+      // check the type of buf in GetLongField above.
+      result.p = baseEnv(env)->GetDirectBufferAddress(env, buf);
+      if (sc.Check(soa, false, "p", &result)) {
+        return const_cast<void*>(result.p);
+      }
+    }
+    return nullptr;
   }
 
   static jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf);
-    // TODO: check that 'buf' is a java.nio.Buffer.
-    return CHECK_JNI_EXIT("J", baseEnv(env)->GetDirectBufferCapacity(env, buf));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = buf}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      // Note: this is implemented in the base environment by a GetIntField which will sanity
+      // check the type of buf in GetIntField above.
+      result.J = baseEnv(env)->GetDirectBufferCapacity(env, buf);
+      if (sc.Check(soa, false, "J", &result)) {
+        return result.J;
+      }
+    }
+    return JNI_ERR;
   }
 
  private:
-  static inline const JNINativeInterface* baseEnv(JNIEnv* env) {
+  static JavaVMExt* GetJavaVMExt(JNIEnv* env) {
+    return reinterpret_cast<JNIEnvExt*>(env)->vm;
+  }
+
+  static const JNINativeInterface* baseEnv(JNIEnv* env) {
     return reinterpret_cast<JNIEnvExt*>(env)->unchecked_functions;
   }
+
+  static jobject NewRef(const char* function_name, JNIEnv* env, jobject obj, IndirectRefKind kind) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      switch (kind) {
+        case kGlobal:
+          result.L = baseEnv(env)->NewGlobalRef(env, obj);
+          break;
+        case kLocal:
+          result.L = baseEnv(env)->NewLocalRef(env, obj);
+          break;
+        case kWeakGlobal:
+          result.L = baseEnv(env)->NewWeakGlobalRef(env, obj);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected reference kind: " << kind;
+      }
+      if (sc.Check(soa, false, "L", &result)) {
+        DCHECK_EQ(IsSameObject(env, obj, result.L), JNI_TRUE);
+        DCHECK(sc.CheckReferenceKind(kind, soa.Vm(), soa.Self(), result.L));
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static void DeleteRef(const char* function_name, JNIEnv* env, jobject obj, IndirectRefKind kind) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, function_name);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    sc.Check(soa, true, "EL", args);
+    if (sc.CheckReferenceKind(kind, soa.Vm(), soa.Self(), obj)) {
+      JniValueType result;
+      switch (kind) {
+        case kGlobal:
+          baseEnv(env)->DeleteGlobalRef(env, obj);
+          break;
+        case kLocal:
+          baseEnv(env)->DeleteLocalRef(env, obj);
+          break;
+        case kWeakGlobal:
+          baseEnv(env)->DeleteWeakGlobalRef(env, obj);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected reference kind: " << kind;
+      }
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jmethodID GetMethodIDInternal(const char* function_name, JNIEnv* env, jclass c,
+                                       const char* name, const char* sig, bool is_static) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[4] = {{.E = env}, {.c = c}, {.u = name}, {.u = sig}};
+    if (sc.Check(soa, true, "Ecuu", args)) {
+      JniValueType result;
+      if (is_static) {
+        result.m = baseEnv(env)->GetStaticMethodID(env, c, name, sig);
+      } else {
+        result.m = baseEnv(env)->GetMethodID(env, c, name, sig);
+      }
+      if (sc.Check(soa, false, "m", &result)) {
+        return result.m;
+      }
+    }
+    return nullptr;
+  }
+
+  static jfieldID GetFieldIDInternal(const char* function_name, JNIEnv* env, jclass c,
+                                     const char* name, const char* sig, bool is_static) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[4] = {{.E = env}, {.c = c}, {.u = name}, {.u = sig}};
+    if (sc.Check(soa, true, "Ecuu", args)) {
+      JniValueType result;
+      if (is_static) {
+        result.f = baseEnv(env)->GetStaticFieldID(env, c, name, sig);
+      } else {
+        result.f = baseEnv(env)->GetFieldID(env, c, name, sig);
+      }
+      if (sc.Check(soa, false, "f", &result)) {
+        return result.f;
+      }
+    }
+    return nullptr;
+  }
+
+  static JniValueType GetField(const char* function_name, JNIEnv* env, jobject obj, jfieldID fid,
+                               bool is_static, Primitive::Type type) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[3] = {{.E = env}, {.L = obj}, {.f = fid}};
+    JniValueType result;
+    if (sc.Check(soa, true, is_static ? "Ecf" : "ELf", args) &&
+        sc.CheckFieldAccess(soa, obj, fid, is_static, type)) {
+      const char* result_check = nullptr;
+      switch (type) {
+        case Primitive::kPrimNot:
+          if (is_static) {
+            result.L = baseEnv(env)->GetStaticObjectField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.L = baseEnv(env)->GetObjectField(env, obj, fid);
+          }
+          result_check = "L";
+          break;
+        case Primitive::kPrimBoolean:
+          if (is_static) {
+            result.Z = baseEnv(env)->GetStaticBooleanField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.Z = baseEnv(env)->GetBooleanField(env, obj, fid);
+          }
+          result_check = "Z";
+          break;
+        case Primitive::kPrimByte:
+          if (is_static) {
+            result.B = baseEnv(env)->GetStaticByteField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.B = baseEnv(env)->GetByteField(env, obj, fid);
+          }
+          result_check = "B";
+          break;
+        case Primitive::kPrimChar:
+          if (is_static) {
+            result.C = baseEnv(env)->GetStaticCharField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.C = baseEnv(env)->GetCharField(env, obj, fid);
+          }
+          result_check = "C";
+          break;
+        case Primitive::kPrimShort:
+          if (is_static) {
+            result.S = baseEnv(env)->GetStaticShortField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.S = baseEnv(env)->GetShortField(env, obj, fid);
+          }
+          result_check = "S";
+          break;
+        case Primitive::kPrimInt:
+          if (is_static) {
+            result.I = baseEnv(env)->GetStaticIntField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.I = baseEnv(env)->GetIntField(env, obj, fid);
+          }
+          result_check = "I";
+          break;
+        case Primitive::kPrimLong:
+          if (is_static) {
+            result.J = baseEnv(env)->GetStaticLongField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.J = baseEnv(env)->GetLongField(env, obj, fid);
+          }
+          result_check = "J";
+          break;
+        case Primitive::kPrimFloat:
+          if (is_static) {
+            result.F = baseEnv(env)->GetStaticFloatField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.F = baseEnv(env)->GetFloatField(env, obj, fid);
+          }
+          result_check = "F";
+          break;
+        case Primitive::kPrimDouble:
+          if (is_static) {
+            result.D = baseEnv(env)->GetStaticDoubleField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.D = baseEnv(env)->GetDoubleField(env, obj, fid);
+          }
+          result_check = "D";
+          break;
+        case Primitive::kPrimVoid:
+          LOG(FATAL) << "Unexpected type: " << type;
+          break;
+      }
+      if (sc.Check(soa, false, result_check, &result)) {
+        return result;
+      }
+    }
+    result.J = 0;
+    return result;
+  }
+
+  static void SetField(const char* function_name, JNIEnv* env, jobject obj, jfieldID fid,
+                       bool is_static, Primitive::Type type, JniValueType value) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[4] = {{.E = env}, {.L = obj}, {.f = fid}, value};
+    char sig[5] = { 'E', is_static ? 'c' : 'L', 'f',
+        type == Primitive::kPrimNot ? 'L' : Primitive::Descriptor(type)[0], '\0'};
+    if (sc.Check(soa, true, sig, args) &&
+        sc.CheckFieldAccess(soa, obj, fid, is_static, type)) {
+      switch (type) {
+        case Primitive::kPrimNot:
+          if (is_static) {
+            baseEnv(env)->SetStaticObjectField(env, down_cast<jclass>(obj), fid, value.L);
+          } else {
+            baseEnv(env)->SetObjectField(env, obj, fid, value.L);
+          }
+          break;
+        case Primitive::kPrimBoolean:
+          if (is_static) {
+            baseEnv(env)->SetStaticBooleanField(env, down_cast<jclass>(obj), fid, value.Z);
+          } else {
+            baseEnv(env)->SetBooleanField(env, obj, fid, value.Z);
+          }
+          break;
+        case Primitive::kPrimByte:
+          if (is_static) {
+            baseEnv(env)->SetStaticByteField(env, down_cast<jclass>(obj), fid, value.B);
+          } else {
+            baseEnv(env)->SetByteField(env, obj, fid, value.B);
+          }
+          break;
+        case Primitive::kPrimChar:
+          if (is_static) {
+            baseEnv(env)->SetStaticCharField(env, down_cast<jclass>(obj), fid, value.C);
+          } else {
+            baseEnv(env)->SetCharField(env, obj, fid, value.C);
+          }
+          break;
+        case Primitive::kPrimShort:
+          if (is_static) {
+            baseEnv(env)->SetStaticShortField(env, down_cast<jclass>(obj), fid, value.S);
+          } else {
+            baseEnv(env)->SetShortField(env, obj, fid, value.S);
+          }
+          break;
+        case Primitive::kPrimInt:
+          if (is_static) {
+            baseEnv(env)->SetStaticIntField(env, down_cast<jclass>(obj), fid, value.I);
+          } else {
+            baseEnv(env)->SetIntField(env, obj, fid, value.I);
+          }
+          break;
+        case Primitive::kPrimLong:
+          if (is_static) {
+            baseEnv(env)->SetStaticLongField(env, down_cast<jclass>(obj), fid, value.J);
+          } else {
+            baseEnv(env)->SetLongField(env, obj, fid, value.J);
+          }
+          break;
+        case Primitive::kPrimFloat:
+          if (is_static) {
+            baseEnv(env)->SetStaticFloatField(env, down_cast<jclass>(obj), fid, value.F);
+          } else {
+            baseEnv(env)->SetFloatField(env, obj, fid, value.F);
+          }
+          break;
+        case Primitive::kPrimDouble:
+          if (is_static) {
+            baseEnv(env)->SetStaticDoubleField(env, down_cast<jclass>(obj), fid, value.D);
+          } else {
+            baseEnv(env)->SetDoubleField(env, obj, fid, value.D);
+          }
+          break;
+        case Primitive::kPrimVoid:
+          LOG(FATAL) << "Unexpected type: " << type;
+          break;
+      }
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static bool CheckCallArgs(ScopedObjectAccess& soa, ScopedCheck& sc, JNIEnv* env, jobject obj,
+                            jclass c, jmethodID mid, InvokeType invoke)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    bool checked;
+    switch (invoke) {
+      case kVirtual: {
+        DCHECK(c == nullptr);
+        JniValueType args[3] = {{.E = env}, {.L = obj}, {.m = mid}};
+        checked = sc.Check(soa, true, "ELm.", args);
+        break;
+      }
+      case kDirect: {
+        JniValueType args[4] = {{.E = env}, {.L = obj}, {.c = c}, {.m = mid}};
+        checked = sc.Check(soa, true, "ELcm.", args);
+        break;
+      }
+      case kStatic: {
+        DCHECK(obj == nullptr);
+        JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}};
+        checked = sc.Check(soa, true, "Ecm.", args);
+        break;
+      }
+      default:
+        LOG(FATAL) << "Unexpected invoke: " << invoke;
+        checked = false;
+        break;
+    }
+    return checked;
+  }
+
+  static JniValueType CallMethodA(const char* function_name, JNIEnv* env, jobject obj, jclass c,
+                                  jmethodID mid, jvalue* vargs, Primitive::Type type,
+                                  InvokeType invoke) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType result;
+    if (CheckCallArgs(soa, sc, env, obj, c, mid, invoke) &&
+        sc.CheckMethodAndSig(soa, obj, c, mid, type, invoke)) {
+      const char* result_check;
+      switch (type) {
+        case Primitive::kPrimNot:
+          result_check = "L";
+          switch (invoke) {
+            case kVirtual:
+              result.L = baseEnv(env)->CallObjectMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.L = baseEnv(env)->CallNonvirtualObjectMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.L = baseEnv(env)->CallStaticObjectMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimBoolean:
+          result_check = "Z";
+          switch (invoke) {
+            case kVirtual:
+              result.Z = baseEnv(env)->CallBooleanMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.Z = baseEnv(env)->CallNonvirtualBooleanMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.Z = baseEnv(env)->CallStaticBooleanMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimByte:
+          result_check = "B";
+          switch (invoke) {
+            case kVirtual:
+              result.B = baseEnv(env)->CallByteMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.B = baseEnv(env)->CallNonvirtualByteMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.B = baseEnv(env)->CallStaticByteMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimChar:
+          result_check = "C";
+          switch (invoke) {
+            case kVirtual:
+              result.C = baseEnv(env)->CallCharMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.C = baseEnv(env)->CallNonvirtualCharMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.C = baseEnv(env)->CallStaticCharMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimShort:
+          result_check = "S";
+          switch (invoke) {
+            case kVirtual:
+              result.S = baseEnv(env)->CallShortMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.S = baseEnv(env)->CallNonvirtualShortMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.S = baseEnv(env)->CallStaticShortMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimInt:
+          result_check = "I";
+          switch (invoke) {
+            case kVirtual:
+              result.I = baseEnv(env)->CallIntMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.I = baseEnv(env)->CallNonvirtualIntMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.I = baseEnv(env)->CallStaticIntMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimLong:
+          result_check = "J";
+          switch (invoke) {
+            case kVirtual:
+              result.J = baseEnv(env)->CallLongMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.J = baseEnv(env)->CallNonvirtualLongMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.J = baseEnv(env)->CallStaticLongMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimFloat:
+          result_check = "F";
+          switch (invoke) {
+            case kVirtual:
+              result.F = baseEnv(env)->CallFloatMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.F = baseEnv(env)->CallNonvirtualFloatMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.F = baseEnv(env)->CallStaticFloatMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimDouble:
+          result_check = "D";
+          switch (invoke) {
+            case kVirtual:
+              result.D = baseEnv(env)->CallDoubleMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.D = baseEnv(env)->CallNonvirtualDoubleMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.D = baseEnv(env)->CallStaticDoubleMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimVoid:
+          result_check = "V";
+          result.V = nullptr;
+          switch (invoke) {
+            case kVirtual:
+              baseEnv(env)->CallVoidMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              baseEnv(env)->CallNonvirtualVoidMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              baseEnv(env)->CallStaticVoidMethodA(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        default:
+          LOG(FATAL) << "Unexpected return type: " << type;
+          result_check = nullptr;
+      }
+      if (sc.Check(soa, false, result_check, &result)) {
+        return result;
+      }
+    }
+    result.J = 0;
+    return result;
+  }
+
+  static JniValueType CallMethodV(const char* function_name, JNIEnv* env, jobject obj, jclass c,
+                                  jmethodID mid, va_list vargs, Primitive::Type type,
+                                  InvokeType invoke) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType result;
+    if (CheckCallArgs(soa, sc, env, obj, c, mid, invoke) &&
+        sc.CheckMethodAndSig(soa, obj, c, mid, type, invoke)) {
+      const char* result_check;
+      switch (type) {
+        case Primitive::kPrimNot:
+          result_check = "L";
+          switch (invoke) {
+            case kVirtual:
+              result.L = baseEnv(env)->CallObjectMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.L = baseEnv(env)->CallNonvirtualObjectMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.L = baseEnv(env)->CallStaticObjectMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimBoolean:
+          result_check = "Z";
+          switch (invoke) {
+            case kVirtual:
+              result.Z = baseEnv(env)->CallBooleanMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.Z = baseEnv(env)->CallNonvirtualBooleanMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.Z = baseEnv(env)->CallStaticBooleanMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimByte:
+          result_check = "B";
+          switch (invoke) {
+            case kVirtual:
+              result.B = baseEnv(env)->CallByteMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.B = baseEnv(env)->CallNonvirtualByteMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.B = baseEnv(env)->CallStaticByteMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimChar:
+          result_check = "C";
+          switch (invoke) {
+            case kVirtual:
+              result.C = baseEnv(env)->CallCharMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.C = baseEnv(env)->CallNonvirtualCharMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.C = baseEnv(env)->CallStaticCharMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimShort:
+          result_check = "S";
+          switch (invoke) {
+            case kVirtual:
+              result.S = baseEnv(env)->CallShortMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.S = baseEnv(env)->CallNonvirtualShortMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.S = baseEnv(env)->CallStaticShortMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimInt:
+          result_check = "I";
+          switch (invoke) {
+            case kVirtual:
+              result.I = baseEnv(env)->CallIntMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.I = baseEnv(env)->CallNonvirtualIntMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.I = baseEnv(env)->CallStaticIntMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimLong:
+          result_check = "J";
+          switch (invoke) {
+            case kVirtual:
+              result.J = baseEnv(env)->CallLongMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.J = baseEnv(env)->CallNonvirtualLongMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.J = baseEnv(env)->CallStaticLongMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimFloat:
+          result_check = "F";
+          switch (invoke) {
+            case kVirtual:
+              result.F = baseEnv(env)->CallFloatMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.F = baseEnv(env)->CallNonvirtualFloatMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.F = baseEnv(env)->CallStaticFloatMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimDouble:
+          result_check = "D";
+          switch (invoke) {
+            case kVirtual:
+              result.D = baseEnv(env)->CallDoubleMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.D = baseEnv(env)->CallNonvirtualDoubleMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.D = baseEnv(env)->CallStaticDoubleMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimVoid:
+          result_check = "V";
+          result.V = nullptr;
+          switch (invoke) {
+            case kVirtual:
+              baseEnv(env)->CallVoidMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              baseEnv(env)->CallNonvirtualVoidMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              baseEnv(env)->CallStaticVoidMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        default:
+          LOG(FATAL) << "Unexpected return type: " << type;
+          result_check = nullptr;
+      }
+      if (sc.Check(soa, false, result_check, &result)) {
+        return result;
+      }
+    }
+    result.J = 0;
+    return result;
+  }
+
+  static const void* GetStringCharsInternal(const char* function_name, JNIEnv* env, jstring string,
+                                            jboolean* is_copy, bool utf, bool critical) {
+    ScopedObjectAccess soa(env);
+    int flags = critical ? kFlag_CritGet : kFlag_CritOkay;
+    ScopedCheck sc(flags, function_name);
+    JniValueType args[3] = {{.E = env}, {.s = string}, {.p = is_copy}};
+    if (sc.Check(soa, true, "Esp", args)) {
+      JniValueType result;
+      if (utf) {
+        CHECK(!critical);
+        result.u = baseEnv(env)->GetStringUTFChars(env, string, is_copy);
+      } else {
+        if (critical) {
+          result.p = baseEnv(env)->GetStringCritical(env, string, is_copy);
+        } else {
+          result.p = baseEnv(env)->GetStringChars(env, string, is_copy);
+        }
+      }
+      // TODO: could we be smarter about not copying when local_is_copy?
+      if (result.p != nullptr && soa.ForceCopy()) {
+        if (utf) {
+          size_t length_in_bytes = strlen(result.u) + 1;
+          result.u =
+              reinterpret_cast<const char*>(GuardedCopy::Create(result.u, length_in_bytes, false));
+        } else {
+          size_t length_in_bytes = baseEnv(env)->GetStringLength(env, string) * 2;
+          result.p =
+              reinterpret_cast<const jchar*>(GuardedCopy::Create(result.p, length_in_bytes, false));
+        }
+        if (is_copy != nullptr) {
+          *is_copy = JNI_TRUE;
+        }
+      }
+      if (sc.Check(soa, false, utf ? "u" : "p", &result)) {
+        return utf ? result.u : result.p;
+      }
+    }
+    return nullptr;
+  }
+
+  static void ReleaseStringCharsInternal(const char* function_name, JNIEnv* env, jstring string,
+                                         const void* chars, bool utf, bool critical) {
+    ScopedObjectAccess soa(env);
+    int flags = kFlag_ExcepOkay | kFlag_Release;
+    if (critical) {
+      flags |= kFlag_CritRelease;
+    }
+    ScopedCheck sc(flags, function_name);
+    sc.CheckNonNull(chars);
+    bool force_copy_ok = !soa.ForceCopy() || GuardedCopy::Check(function_name, chars, false);
+    if (force_copy_ok && soa.ForceCopy()) {
+      chars = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<void*>(chars)));
+    }
+    if (force_copy_ok) {
+      JniValueType args[3] = {{.E = env}, {.s = string}, {.p = chars}};
+      if (sc.Check(soa, true, utf ? "Esu" : "Esp", args)) {
+        if (utf) {
+          CHECK(!critical);
+          baseEnv(env)->ReleaseStringUTFChars(env, string, reinterpret_cast<const char*>(chars));
+        } else {
+          if (critical) {
+            baseEnv(env)->ReleaseStringCritical(env, string, reinterpret_cast<const jchar*>(chars));
+          } else {
+            baseEnv(env)->ReleaseStringChars(env, string, reinterpret_cast<const jchar*>(chars));
+          }
+        }
+        JniValueType result;
+        sc.Check(soa, false, "V", &result);
+      }
+    }
+  }
+
+  static jarray NewPrimitiveArray(const char* function_name, JNIEnv* env, jsize length,
+                                  Primitive::Type type) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.z = length}};
+    if (sc.Check(soa, true, "Ez", args)) {
+      JniValueType result;
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          result.a = baseEnv(env)->NewBooleanArray(env, length);
+          break;
+        case Primitive::kPrimByte:
+          result.a = baseEnv(env)->NewByteArray(env, length);
+          break;
+        case Primitive::kPrimChar:
+          result.a = baseEnv(env)->NewCharArray(env, length);
+          break;
+        case Primitive::kPrimShort:
+          result.a = baseEnv(env)->NewShortArray(env, length);
+          break;
+        case Primitive::kPrimInt:
+          result.a = baseEnv(env)->NewIntArray(env, length);
+          break;
+        case Primitive::kPrimLong:
+          result.a = baseEnv(env)->NewLongArray(env, length);
+          break;
+        case Primitive::kPrimFloat:
+          result.a = baseEnv(env)->NewFloatArray(env, length);
+          break;
+        case Primitive::kPrimDouble:
+          result.a = baseEnv(env)->NewDoubleArray(env, length);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      if (sc.Check(soa, false, "a", &result)) {
+        return result.a;
+      }
+    }
+    return nullptr;
+  }
+
+  static void* GetPrimitiveArrayElements(const char* function_name, Primitive::Type type,
+                                         JNIEnv* env, jarray array, jboolean* is_copy) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[3] = {{.E = env}, {.a = array}, {.p = is_copy}};
+    if (sc.Check(soa, true, "Eap", args) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      JniValueType result;
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          result.p = baseEnv(env)->GetBooleanArrayElements(env, down_cast<jbooleanArray>(array),
+                                                           is_copy);
+          break;
+        case Primitive::kPrimByte:
+          result.p = baseEnv(env)->GetByteArrayElements(env, down_cast<jbyteArray>(array),
+                                                        is_copy);
+          break;
+        case Primitive::kPrimChar:
+          result.p = baseEnv(env)->GetCharArrayElements(env, down_cast<jcharArray>(array),
+                                                        is_copy);
+          break;
+        case Primitive::kPrimShort:
+          result.p = baseEnv(env)->GetShortArrayElements(env, down_cast<jshortArray>(array),
+                                                         is_copy);
+          break;
+        case Primitive::kPrimInt:
+          result.p = baseEnv(env)->GetIntArrayElements(env, down_cast<jintArray>(array), is_copy);
+          break;
+        case Primitive::kPrimLong:
+          result.p = baseEnv(env)->GetLongArrayElements(env, down_cast<jlongArray>(array),
+                                                        is_copy);
+          break;
+        case Primitive::kPrimFloat:
+          result.p = baseEnv(env)->GetFloatArrayElements(env, down_cast<jfloatArray>(array),
+                                                         is_copy);
+          break;
+        case Primitive::kPrimDouble:
+          result.p = baseEnv(env)->GetDoubleArrayElements(env, down_cast<jdoubleArray>(array),
+                                                          is_copy);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      if (result.p != nullptr && soa.ForceCopy()) {
+        result.p = GuardedCopy::CreateGuardedPACopy(env, array, is_copy);
+        if (is_copy != nullptr) {
+          *is_copy = JNI_TRUE;
+        }
+      }
+      if (sc.Check(soa, false, "p", &result)) {
+        return const_cast<void*>(result.p);
+      }
+    }
+    return nullptr;
+  }
+
+  static void ReleasePrimitiveArrayElements(const char* function_name, Primitive::Type type,
+                                            JNIEnv* env, jarray array, void* elems, jint mode) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, function_name);
+    if (sc.CheckNonNull(elems) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      if (soa.ForceCopy()) {
+        elems = GuardedCopy::ReleaseGuardedPACopy(function_name, env, array, elems, mode);
+      }
+      if (!soa.ForceCopy() || elems != nullptr) {
+        JniValueType args[4] = {{.E = env}, {.a = array}, {.p = elems}, {.r = mode}};
+        if (sc.Check(soa, true, "Eapr", args)) {
+          switch (type) {
+            case Primitive::kPrimBoolean:
+              baseEnv(env)->ReleaseBooleanArrayElements(env, down_cast<jbooleanArray>(array),
+                                                        reinterpret_cast<jboolean*>(elems), mode);
+              break;
+            case Primitive::kPrimByte:
+              baseEnv(env)->ReleaseByteArrayElements(env, down_cast<jbyteArray>(array),
+                                                     reinterpret_cast<jbyte*>(elems), mode);
+              break;
+            case Primitive::kPrimChar:
+              baseEnv(env)->ReleaseCharArrayElements(env, down_cast<jcharArray>(array),
+                                                     reinterpret_cast<jchar*>(elems), mode);
+              break;
+            case Primitive::kPrimShort:
+              baseEnv(env)->ReleaseShortArrayElements(env, down_cast<jshortArray>(array),
+                                                      reinterpret_cast<jshort*>(elems), mode);
+              break;
+            case Primitive::kPrimInt:
+              baseEnv(env)->ReleaseIntArrayElements(env, down_cast<jintArray>(array),
+                                                    reinterpret_cast<jint*>(elems), mode);
+              break;
+            case Primitive::kPrimLong:
+              baseEnv(env)->ReleaseLongArrayElements(env, down_cast<jlongArray>(array),
+                                                     reinterpret_cast<jlong*>(elems), mode);
+              break;
+            case Primitive::kPrimFloat:
+              baseEnv(env)->ReleaseFloatArrayElements(env, down_cast<jfloatArray>(array),
+                                                      reinterpret_cast<jfloat*>(elems), mode);
+              break;
+            case Primitive::kPrimDouble:
+              baseEnv(env)->ReleaseDoubleArrayElements(env, down_cast<jdoubleArray>(array),
+                                                       reinterpret_cast<jdouble*>(elems), mode);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected primitive type: " << type;
+          }
+          JniValueType result;
+          result.V = nullptr;
+          sc.Check(soa, false, "V", &result);
+        }
+      }
+    }
+  }
+
+  static void GetPrimitiveArrayRegion(const char* function_name, Primitive::Type type, JNIEnv* env,
+                                      jarray array, jsize start, jsize len, void* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[5] = {{.E = env}, {.a = array}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EaIIp", args) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          baseEnv(env)->GetBooleanArrayRegion(env, down_cast<jbooleanArray>(array), start, len,
+                                              reinterpret_cast<jboolean*>(buf));
+          break;
+        case Primitive::kPrimByte:
+          baseEnv(env)->GetByteArrayRegion(env, down_cast<jbyteArray>(array), start, len,
+                                           reinterpret_cast<jbyte*>(buf));
+          break;
+        case Primitive::kPrimChar:
+          baseEnv(env)->GetCharArrayRegion(env, down_cast<jcharArray>(array), start, len,
+                                           reinterpret_cast<jchar*>(buf));
+          break;
+        case Primitive::kPrimShort:
+          baseEnv(env)->GetShortArrayRegion(env, down_cast<jshortArray>(array), start, len,
+                                            reinterpret_cast<jshort*>(buf));
+          break;
+        case Primitive::kPrimInt:
+          baseEnv(env)->GetIntArrayRegion(env, down_cast<jintArray>(array), start, len,
+                                          reinterpret_cast<jint*>(buf));
+          break;
+        case Primitive::kPrimLong:
+          baseEnv(env)->GetLongArrayRegion(env, down_cast<jlongArray>(array), start, len,
+                                           reinterpret_cast<jlong*>(buf));
+          break;
+        case Primitive::kPrimFloat:
+          baseEnv(env)->GetFloatArrayRegion(env, down_cast<jfloatArray>(array), start, len,
+                                            reinterpret_cast<jfloat*>(buf));
+          break;
+        case Primitive::kPrimDouble:
+          baseEnv(env)->GetDoubleArrayRegion(env, down_cast<jdoubleArray>(array), start, len,
+                                             reinterpret_cast<jdouble*>(buf));
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static void SetPrimitiveArrayRegion(const char* function_name, Primitive::Type type, JNIEnv* env,
+                                      jarray array, jsize start, jsize len, const void* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[5] = {{.E = env}, {.a = array}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EaIIp", args) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          baseEnv(env)->SetBooleanArrayRegion(env, down_cast<jbooleanArray>(array), start, len,
+                                              reinterpret_cast<const jboolean*>(buf));
+          break;
+        case Primitive::kPrimByte:
+          baseEnv(env)->SetByteArrayRegion(env, down_cast<jbyteArray>(array), start, len,
+                                           reinterpret_cast<const jbyte*>(buf));
+          break;
+        case Primitive::kPrimChar:
+          baseEnv(env)->SetCharArrayRegion(env, down_cast<jcharArray>(array), start, len,
+                                           reinterpret_cast<const jchar*>(buf));
+          break;
+        case Primitive::kPrimShort:
+          baseEnv(env)->SetShortArrayRegion(env, down_cast<jshortArray>(array), start, len,
+                                              reinterpret_cast<const jshort*>(buf));
+          break;
+        case Primitive::kPrimInt:
+          baseEnv(env)->SetIntArrayRegion(env, down_cast<jintArray>(array), start, len,
+                                          reinterpret_cast<const jint*>(buf));
+          break;
+        case Primitive::kPrimLong:
+          baseEnv(env)->SetLongArrayRegion(env, down_cast<jlongArray>(array), start, len,
+                                              reinterpret_cast<const jlong*>(buf));
+          break;
+        case Primitive::kPrimFloat:
+          baseEnv(env)->SetFloatArrayRegion(env, down_cast<jfloatArray>(array), start, len,
+                                            reinterpret_cast<const jfloat*>(buf));
+          break;
+        case Primitive::kPrimDouble:
+          baseEnv(env)->SetDoubleArrayRegion(env, down_cast<jdoubleArray>(array), start, len,
+                                             reinterpret_cast<const jdouble*>(buf));
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
 };
 
 const JNINativeInterface gCheckNativeInterface = {
@@ -2023,38 +3584,58 @@
 class CheckJII {
  public:
   static jint DestroyJavaVM(JavaVM* vm) {
-    ScopedCheck sc(vm, false, __FUNCTION__);
-    sc.Check(true, "v", vm);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->DestroyJavaVM(vm));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__, false);
+    JniValueType args[1] = {{.v = vm}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "v", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->DestroyJavaVM(vm);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
   static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-    ScopedCheck sc(vm, false, __FUNCTION__);
-    sc.Check(true, "vpp", vm, p_env, thr_args);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThread(vm, p_env, thr_args));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.p = thr_args}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpp", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->AttachCurrentThread(vm, p_env, thr_args);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
   static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-    ScopedCheck sc(vm, false, __FUNCTION__);
-    sc.Check(true, "vpp", vm, p_env, thr_args);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.p = thr_args}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpp", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
   static jint DetachCurrentThread(JavaVM* vm) {
-    ScopedCheck sc(vm, true, __FUNCTION__);
-    sc.Check(true, "v", vm);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->DetachCurrentThread(vm));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[1] = {{.v = vm}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "v", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->DetachCurrentThread(vm);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
-  static jint GetEnv(JavaVM* vm, void** env, jint version) {
-    ScopedCheck sc(vm, true, __FUNCTION__);
-    sc.Check(true, "vpI", vm);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->GetEnv(vm, env, version));
+  static jint GetEnv(JavaVM* vm, void** p_env, jint version) {
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.I = version}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpI", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->GetEnv(vm, p_env, version);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
  private:
-  static inline const JNIInvokeInterface* BaseVm(JavaVM* vm) {
-    return reinterpret_cast<JavaVMExt*>(vm)->unchecked_functions;
+  static const JNIInvokeInterface* BaseVm(JavaVM* vm) {
+    return reinterpret_cast<JavaVMExt*>(vm)->GetUncheckedFunctions();
   }
 };