SIRT work around JNI support for legacy apps

Legacy apps may be broken and expect a jobject to equal an Object*. This
is a work around to support outgoing SIRT arguments being converted to
be Object*s. It steals the unused GC map pointer on native methods.

Change-Id: I8fb3701b0cdf15be9e8f9b722e47386505482666
diff --git a/src/dalvik_system_VMRuntime.cc b/src/dalvik_system_VMRuntime.cc
index 6f9480d..10732db 100644
--- a/src/dalvik_system_VMRuntime.cc
+++ b/src/dalvik_system_VMRuntime.cc
@@ -125,10 +125,10 @@
   // Note that targetSdkVersion may be CUR_DEVELOPMENT (10000).
   // Note that targetSdkVersion may be 0, meaning "current".
   if (targetSdkVersion > 0 && targetSdkVersion <= 13 /* honeycomb-mr2 */) {
+    JNIEnvExt* env_ext = reinterpret_cast<JNIEnvExt*>(env);
     // TODO: running with CheckJNI should override this and force you to obey the strictest rules.
     LOG(INFO) << "Turning on JNI app bug workarounds for target SDK version " << targetSdkVersion << "...";
-    // Runtime::Current()->GetJavaVM()->work_around_app_jni_bugs = true;
-    UNIMPLEMENTED(WARNING) << "Support work arounds for app JNI bugs";
+    env_ext->vm->work_around_app_jni_bugs = true;
   }
 }
 
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index 8b61226..d0a6776 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -2252,7 +2252,7 @@
 
       VLOG(jni) << "[Registering JNI native method " << PrettyMethod(m) << "]";
 
-      m->RegisterNative(methods[i].fnPtr);
+      m->RegisterNative(ts.Self(), methods[i].fnPtr);
     }
     return JNI_OK;
   }
diff --git a/src/object.cc b/src/object.cc
index 3aba5c8..cf15a64 100644
--- a/src/object.cc
+++ b/src/object.cc
@@ -34,6 +34,7 @@
 #include "monitor.h"
 #include "object_utils.h"
 #include "runtime.h"
+#include "runtime_support.h"
 #include "stack.h"
 #include "utils.h"
 
@@ -598,17 +599,34 @@
   return native_method != jni_stub;
 }
 
-void Method::RegisterNative(const void* native_method) {
+void Method::RegisterNative(Thread* self, const void* native_method) {
+  DCHECK(Thread::Current() == self);
   CHECK(IsNative()) << PrettyMethod(this);
   CHECK(native_method != NULL) << PrettyMethod(this);
-  SetFieldPtr<const void*>(OFFSET_OF_OBJECT_MEMBER(Method, native_method_),
-                           native_method, false);
+  if (!self->GetJniEnv()->vm->work_around_app_jni_bugs) {
+    SetFieldPtr<const void*>(OFFSET_OF_OBJECT_MEMBER(Method, native_method_),
+                             native_method, false);
+  } else {
+    // We've been asked to associate this method with the given native method but are working
+    // around JNI bugs, that include not giving Object** SIRT references to native methods. Direct
+    // the native method to runtime support and store the target somewhere runtime support will
+    // find it.
+#if defined(__arm__)
+    SetFieldPtr<const void*>(OFFSET_OF_OBJECT_MEMBER(Method, native_method_),
+        reinterpret_cast<const void*>(art_work_around_app_jni_bugs), false);
+#else
+    UNIMPLEMENTED(FATAL);
+#endif
+    SetFieldPtr<const uint8_t*>(OFFSET_OF_OBJECT_MEMBER(Method, gc_map_),
+        reinterpret_cast<const uint8_t*>(native_method), false);
+  }
 }
 
 void Method::UnregisterNative() {
   CHECK(IsNative()) << PrettyMethod(this);
   // restore stub to lookup native pointer via dlsym
-  RegisterNative(Runtime::Current()->GetJniDlsymLookupStub()->GetData());
+  SetFieldPtr<const void*>(OFFSET_OF_OBJECT_MEMBER(Method, native_method_),
+      Runtime::Current()->GetJniDlsymLookupStub()->GetData(), false);
 }
 
 void Class::SetStatus(Status new_status) {
diff --git a/src/object.h b/src/object.h
index f9c8eb7..da8064f 100644
--- a/src/object.h
+++ b/src/object.h
@@ -762,7 +762,7 @@
 
   bool IsRegistered() const;
 
-  void RegisterNative(const void* native_method);
+  void RegisterNative(Thread* self, const void* native_method);
 
   void UnregisterNative();
 
diff --git a/src/runtime_support.cc b/src/runtime_support.cc
index f158680..d9d7e90 100644
--- a/src/runtime_support.cc
+++ b/src/runtime_support.cc
@@ -131,21 +131,21 @@
   return thread->DecodeJObject(obj);
 }
 
-extern void* FindNativeMethod(Thread* thread) {
-  DCHECK(Thread::Current() == thread);
+extern void* FindNativeMethod(Thread* self) {
+  DCHECK(Thread::Current() == self);
 
-  Method* method = const_cast<Method*>(thread->GetCurrentMethod());
+  Method* method = const_cast<Method*>(self->GetCurrentMethod());
   DCHECK(method != NULL);
 
   // Lookup symbol address for method, on failure we'll return NULL with an
   // exception set, otherwise we return the address of the method we found.
-  void* native_code = thread->GetJniEnv()->vm->FindCodeForNativeMethod(method);
+  void* native_code = self->GetJniEnv()->vm->FindCodeForNativeMethod(method);
   if (native_code == NULL) {
-    DCHECK(thread->IsExceptionPending());
+    DCHECK(self->IsExceptionPending());
     return NULL;
   } else {
     // Register so that future calls don't come here
-    method->RegisterNative(native_code);
+    method->RegisterNative(self, native_code);
     return native_code;
   }
 }
@@ -510,6 +510,74 @@
   return code;
 }
 
+static void WorkAroundJniBugsForJobject(intptr_t* arg_ptr) {
+  intptr_t value = *arg_ptr;
+  Object** value_as_jni_rep = reinterpret_cast<Object**>(value);
+  Object* value_as_work_around_rep = value_as_jni_rep != NULL ? *value_as_jni_rep : NULL;
+  CHECK(Heap::IsHeapAddress(value_as_work_around_rep));
+  *arg_ptr = reinterpret_cast<intptr_t>(value_as_work_around_rep);
+}
+
+extern "C" const void* artWorkAroundAppJniBugs(Thread* self, intptr_t* sp) {
+  DCHECK(Thread::Current() == self);
+  // TODO: this code is specific to ARM
+  // On entry the stack pointed by sp is:
+  // | arg3   | <- Calling JNI method's frame (and extra bit for out args)
+  // | LR     |
+  // | R3     |    arg2
+  // | R2     |    arg1
+  // | R1     |    jclass/jobject
+  // | R0     |    JNIEnv
+  // | unused |
+  // | unused |
+  // | unused | <- sp
+  Method* jni_method = self->GetTopOfStack().GetMethod();
+  DCHECK(jni_method->IsNative()) << PrettyMethod(jni_method);
+  intptr_t* arg_ptr = sp + 4;  // pointer to r1 on stack
+  // Fix up this/jclass argument
+  WorkAroundJniBugsForJobject(arg_ptr);
+  arg_ptr++;
+  // Fix up jobject arguments
+  MethodHelper mh(jni_method);
+  int reg_num = 2;  // Current register being processed, -1 for stack arguments.
+  for (int32_t i = 1; i < mh.GetShortyLength(); i++) {
+    char shorty_char = mh.GetShorty()[i];
+    if (shorty_char == 'L') {
+      WorkAroundJniBugsForJobject(arg_ptr);
+    }
+    if (shorty_char == 'J' || shorty_char == 'D') {
+      if (reg_num == 2) {
+        arg_ptr = sp + 8;  // skip to out arguments
+        reg_num = -1;
+      } else if (reg_num == 3) {
+        arg_ptr = sp + 10;  // skip to out arguments plus 2 slots as long must be aligned
+        reg_num = -1;
+      } else {
+        DCHECK(reg_num == -1);
+        if ((reinterpret_cast<intptr_t>(arg_ptr) & 7) == 4) {
+          arg_ptr += 3;  // unaligned, pad and move through stack arguments
+        } else {
+          arg_ptr += 2;  // aligned, move through stack arguments
+        }
+      }
+    } else {
+      if (reg_num == 2) {
+        arg_ptr++; // move through register arguments
+        reg_num++;
+      } else if (reg_num == 3) {
+        arg_ptr = sp + 8;  // skip to outgoing stack arguments
+        reg_num = -1;
+      } else {
+        DCHECK(reg_num == -1);
+        arg_ptr++;  // move through stack arguments
+      }
+    }
+  }
+  // Load expected destination, see Method::RegisterNative
+  return reinterpret_cast<const void*>(jni_method->GetGcMapRaw());
+}
+
+
 // Fast path field resolution that can't throw exceptions
 static Field* FindFieldFast(uint32_t field_idx, const Method* referrer, bool is_primitive,
                             size_t expected_size) {
diff --git a/src/runtime_support.h b/src/runtime_support.h
index 305bbf6..97197e6 100644
--- a/src/runtime_support.h
+++ b/src/runtime_support.h
@@ -99,6 +99,7 @@
   extern "C" void* art_resolve_string_from_code(void*, uint32_t);
   extern "C" void* art_resolve_method_from_code(void* referrer, uint32_t method_idx, bool is_direct);
   extern "C" void art_update_debugger(void*, void*, int32_t, void*);
+  extern "C" void art_work_around_app_jni_bugs();
 
   /* Conversions */
   extern "C" float __aeabi_i2f(int op1);             // OP_INT_TO_FLOAT
diff --git a/src/runtime_support_arm.S b/src/runtime_support_arm.S
index b337ebc..efa6ef8 100644
--- a/src/runtime_support_arm.S
+++ b/src/runtime_support_arm.S
@@ -210,6 +210,23 @@
 INVOKE_TRAMPOLINE art_invoke_super_trampoline_with_access_check, artInvokeSuperTrampolineWithAccessCheck
 INVOKE_TRAMPOLINE art_invoke_virtual_trampoline_with_access_check, artInvokeVirtualTrampolineWithAccessCheck
 
+    .global art_work_around_app_jni_bugs
+    .extern artWorkAroundAppJniBugs
+    /*
+     * Entry point of native methods when JNI bug compatibility is enabled.
+     */
+art_work_around_app_jni_bugs:
+    @ save registers that may contain arguments and LR that will be crushed by a call
+    push {r0-r3, lr}
+    sub sp, #12      @ 3 words of space for alignment
+    mov r0, r9       @ pass Thread::Current
+    mov r1, sp       @ pass SP
+    bl  artWorkAroundAppJniBugs  @ (Thread*, SP)
+    add sp, #12      @ rewind stack
+    mov r12, r0      @ save target address
+    pop {r0-r3, lr}  @ restore possibly modified argument registers
+    bx  r12          @ tail call into JNI routine
+
     .global art_handle_fill_data_from_code
     .extern artHandleFillArrayDataFromCode
     /*