Intercept JNI invocation of String.<init> methods.

libmono uses JNI AllocObject and CallNonvirtualVoidMethod to create and
initialize a string instead of using the recommended NewObject. This
change adds an intercept to change the String.<init> call to a
StringFactory call instead. Then, it uses the object id of the original
string object referrer and maps it to the result of the StringFactory.

Bug: 21288130

(cherry picked from commit 15e9ad1d028d7f12cb598b075453173532a00d91)

Change-Id: I3421c43722c07397da4a398c2ca9110e1d40bcfa
diff --git a/test/004-JniTest/jni_test.cc b/test/004-JniTest/jni_test.cc
index cdc5461..1ec0cf2 100644
--- a/test/004-JniTest/jni_test.cc
+++ b/test/004-JniTest/jni_test.cc
@@ -550,21 +550,58 @@
 }
 
 extern "C" JNIEXPORT void JNICALL Java_Main_testNewStringObject(JNIEnv* env, jclass) {
-  const char* string = "Test";
-  int length = strlen(string);
   jclass c = env->FindClass("java/lang/String");
-  assert(c != NULL);
-  jmethodID method = env->GetMethodID(c, "<init>", "([B)V");
-  assert(method != NULL);
+  assert(c != nullptr);
+
+  jmethodID mid1 = env->GetMethodID(c, "<init>", "()V");
+  assert(mid1 != nullptr);
   assert(!env->ExceptionCheck());
-  jbyteArray array = env->NewByteArray(length);
-  env->SetByteArrayRegion(array, 0, length, reinterpret_cast<const jbyte*>(string));
-  jobject o = env->NewObject(c, method, array);
-  assert(o != NULL);
-  jstring s = reinterpret_cast<jstring>(o);
-  assert(env->GetStringLength(s) == length);
-  assert(env->GetStringUTFLength(s) == length);
+  jmethodID mid2 = env->GetMethodID(c, "<init>", "([B)V");
+  assert(mid2 != nullptr);
+  assert(!env->ExceptionCheck());
+  jmethodID mid3 = env->GetMethodID(c, "<init>", "([C)V");
+  assert(mid3 != nullptr);
+  assert(!env->ExceptionCheck());
+  jmethodID mid4 = env->GetMethodID(c, "<init>", "(Ljava/lang/String;)V");
+  assert(mid4 != nullptr);
+  assert(!env->ExceptionCheck());
+
+  const char* test_array = "Test";
+  int byte_array_length = strlen(test_array);
+  jbyteArray byte_array = env->NewByteArray(byte_array_length);
+  env->SetByteArrayRegion(byte_array, 0, byte_array_length, reinterpret_cast<const jbyte*>(test_array));
+
+  // Test NewObject
+  jstring s = reinterpret_cast<jstring>(env->NewObject(c, mid2, byte_array));
+  assert(s != nullptr);
+  assert(env->GetStringLength(s) == byte_array_length);
+  assert(env->GetStringUTFLength(s) == byte_array_length);
   const char* chars = env->GetStringUTFChars(s, nullptr);
-  assert(strcmp(string, chars) == 0);
+  assert(strcmp(test_array, chars) == 0);
   env->ReleaseStringUTFChars(s, chars);
+
+  // Test AllocObject and Call(Nonvirtual)VoidMethod
+  jstring s1 = reinterpret_cast<jstring>(env->AllocObject(c));
+  assert(s1 != nullptr);
+  jstring s2 = reinterpret_cast<jstring>(env->AllocObject(c));
+  assert(s2 != nullptr);
+  jstring s3 = reinterpret_cast<jstring>(env->AllocObject(c));
+  assert(s3 != nullptr);
+  jstring s4 = reinterpret_cast<jstring>(env->AllocObject(c));
+  assert(s4 != nullptr);
+
+  jcharArray char_array = env->NewCharArray(5);
+  jstring string_arg = env->NewStringUTF("helloworld");
+
+  // With Var Args
+  env->CallVoidMethod(s1, mid1);
+  env->CallNonvirtualVoidMethod(s2, c, mid2, byte_array);
+
+  // With JValues
+  jvalue args3[1];
+  args3[0].l = char_array;
+  jvalue args4[1];
+  args4[0].l = string_arg;
+  env->CallVoidMethodA(s3, mid3, args3);
+  env->CallNonvirtualVoidMethodA(s4, c, mid4, args4);
 }
diff --git a/test/020-string/src/Main.java b/test/020-string/src/Main.java
index bb8ce1f..b876e6a 100644
--- a/test/020-string/src/Main.java
+++ b/test/020-string/src/Main.java
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+import java.nio.charset.Charset;
+import java.io.UnsupportedEncodingException;
+
 /**
  * Simple string test.
  */
@@ -21,6 +24,7 @@
     public static void main(String args[]) {
         basicTest();
         indexTest();
+        constructorTest();
     }
 
     public static void basicTest() {
@@ -81,4 +85,36 @@
             subStr.indexOf('&') + ":" +
             baseStr.indexOf(0x12341234));
     }
+
+    public static void constructorTest() {
+        byte[] byteArray = "byteArray".getBytes();
+        char[] charArray = new char[] { 'c', 'h', 'a', 'r', 'A', 'r', 'r', 'a', 'y' };
+        String charsetName = "US-ASCII";
+        Charset charset = Charset.forName("UTF-8");
+        String string = "string";
+        StringBuffer stringBuffer = new StringBuffer("stringBuffer");
+        int [] codePoints = new int[] { 65, 66, 67, 68, 69 };
+        StringBuilder stringBuilder = new StringBuilder("stringBuilder");
+
+        String s1 = new String();
+        String s2 = new String(byteArray);
+        String s3 = new String(byteArray, 1);
+        String s4 = new String(byteArray, 0, 4);
+        String s5 = new String(byteArray, 2, 4, 5);
+
+        try {
+            String s6 = new String(byteArray, 2, 4, charsetName);
+            String s7 = new String(byteArray, charsetName);
+        } catch (UnsupportedEncodingException e) {
+            System.out.println("Got unexpected UnsupportedEncodingException");
+        }
+        String s8 = new String(byteArray, 3, 3, charset);
+        String s9 = new String(byteArray, charset);
+        String s10 = new String(charArray);
+        String s11 = new String(charArray, 0, 4);
+        String s12 = new String(string);
+        String s13 = new String(stringBuffer);
+        String s14 = new String(codePoints, 1, 3);
+        String s15 = new String(stringBuilder);
+    }
 }