Require mutator lock for DeleteLocalRef

There was a race condition where suspended threads could call
DeleteLocalRef while the GC was marking their roots. This could
cause the GC to attempt to mark a null object.

Bug: 22119403

Change-Id: I962c717bb87b2acb2a4710a2d7ab16793e031401
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index cb36183..cc176b7 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -554,15 +554,16 @@
     return soa.AddLocalReference<jobject>(decoded_obj);
   }
 
-  static void DeleteLocalRef(JNIEnv* env, jobject obj)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  static void DeleteLocalRef(JNIEnv* env, jobject obj) {
     if (obj == nullptr) {
       return;
     }
-    IndirectReferenceTable& locals = reinterpret_cast<JNIEnvExt*>(env)->locals;
-
-    uint32_t cookie = reinterpret_cast<JNIEnvExt*>(env)->local_ref_cookie;
-    if (!locals.Remove(cookie, obj)) {
+    // SOA is only necessary to have exclusion between GC root marking and removing.
+    // We don't want to have the GC attempt to mark a null root if we just removed
+    // it. b/22119403
+    ScopedObjectAccess soa(env);
+    auto* ext_env = down_cast<JNIEnvExt*>(env);
+    if (!ext_env->locals.Remove(ext_env->local_ref_cookie, obj)) {
       // Attempting to delete a local reference that is not in the
       // topmost local reference frame is a no-op.  DeleteLocalRef returns
       // void and doesn't throw any exceptions, but we should probably