Add resize arrays JVMTI extension
As a prototype for more general object replacement functionality add a
new JVMTI extension that allows one to change the size of arrays. This
extension is 'com.android.art.heap.change_array_size'. As far as any
JVMTI agent, JNI or Java Language code can observer this extension
atomically replaces every reference (strong and weak, global and
local, etc.) with a newly allocated array with the same contents but a
different length. Internally a whole new array will be created then
the old array will have its contents (including lock-word) copied and
all references to the old array will be replaced with the new array.
Test: ./test.py --host
Bug: 134162467
Change-Id: I92a0beabb02e0c92c8c8f9639836014ff1266878
diff --git a/test/1974-resize-array/expected.txt b/test/1974-resize-array/expected.txt
new file mode 100644
index 0000000..4b0d432
--- /dev/null
+++ b/test/1974-resize-array/expected.txt
@@ -0,0 +1,78 @@
+Test instance
+val is: [1, 2, 3] resize +3
+val is: [1, 2, 3, 0, 0, 0, 0, 0]
+Same value? true
+
+Test HashMap
+val is: [1, 2, 3, 4] resize +3
+Map is: ([1, 2, 3, 4]->Other Value), ([1, 2, 3, 4]->THE VALUE), ([1, 4]->Third value),
+val is: [1, 2, 3, 4, 0, 0, 0]
+Map is: ([1, 2, 3, 4]->Other Value), ([1, 2, 3, 4, 0, 0, 0]->THE VALUE), ([1, 4]->Third value),
+
+Test j.l.r.WeakReference
+val is: [weak, ref] resize +3
+val is: [weak, ref, null, null, null, null, null]
+Same value? true
+
+Test instance self-ref
+val is: [<SELF REF>, A, B, C] resize +5 item 0 is [<SELF REF>, A, B, C]
+val is: [<SELF REF>, A, B, C, null, null, null, null, null]
+val is: [<SELF REF>, A, B, C, null, null, null, null, null]
+Same value? true
+Same structure? true
+Same inner-structure? true
+
+Test instance self-ref smaller
+val is: [<SELF REF>, A, B, C, null, null, null, null, null] resize -7 item 0 is [<SELF REF>, A, B, C, null, null, null, null, null]
+val is: [<SELF REF>, A]
+val is: [<SELF REF>, A]
+Same value? true
+Same structure? true
+Same inner-structure? true
+
+Test local
+val is: [2, 3, 4] resize +5
+val is: [2, 3, 4, 0, 0, 0, 0, 0]
+Same value? true
+
+Test local smaller
+val is: [1, 2, 3, 4, 5] resize -2
+val is: [1, 2, 3]
+Same value? true
+
+T1: Test local multi-thread
+T1: val is: [1, 2, 3] resize -2
+T1: val is: [1]
+T1: Same value? true
+T2: Test local multi-thread
+T2: val is: [1, 2, 3] resize -2
+T2: val is: [1]
+T2: Same value? true
+
+Test locks
+val is: [A, 2, C] resize -2
+val is: [A]
+Same value? true
+Locks seem to all work.
+
+Test jni-ref
+val is: [1, 11, 111] resize +5
+val is: [1, 11, 111, null, null, null, null, null]
+Same value? true
+
+Test weak jni-ref
+val is: [2, 22, 222] resize +5
+val is: [2, 22, 222, null, null, null, null, null]
+Same value? true
+
+Test jni local ref
+val is: [3, 32, 322]
+Resize +4
+val is: [3, 32, 322, null, null, null, null]
+Same value? true
+
+Test jvmti-tags
+val is: [[3, 33, 333]] resize +5
+val is: [[3, 33, 333, null, null, null, null, null]]
+Same value? true
+
diff --git a/test/1974-resize-array/info.txt b/test/1974-resize-array/info.txt
new file mode 100644
index 0000000..ef4fa40
--- /dev/null
+++ b/test/1974-resize-array/info.txt
@@ -0,0 +1,3 @@
+Test for change_array_size extension function.
+
+Tests that we are able to use the extension function to change the length of arrays.
diff --git a/test/1974-resize-array/resize_array.cc b/test/1974-resize-array/resize_array.cc
new file mode 100644
index 0000000..dadcbea
--- /dev/null
+++ b/test/1974-resize-array/resize_array.cc
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+// Test infrastructure
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test1974ResizeArray {
+
+using ChangeArraySize = jvmtiError (*)(jvmtiEnv* env, jobject arr, jint size);
+
+template <typename T> static void Dealloc(T* t) {
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename... Rest> static void Dealloc(T* t, Rest... rs) {
+ Dealloc(t);
+ Dealloc(rs...);
+}
+
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+ for (jint i = 0; i < n_params; i++) {
+ Dealloc(params[i].name);
+ }
+}
+
+static jvmtiExtensionFunction FindExtensionMethod(JNIEnv* env, const std::string& name) {
+ jint n_ext;
+ jvmtiExtensionFunctionInfo* infos;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+ return nullptr;
+ }
+ jvmtiExtensionFunction res = nullptr;
+ for (jint i = 0; i < n_ext; i++) {
+ jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+ if (strcmp(name.c_str(), cur_info->id) == 0) {
+ res = cur_info->func;
+ }
+ // Cleanup the cur_info
+ DeallocParams(cur_info->params, cur_info->param_count);
+ Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+ }
+ // Cleanup the array.
+ Dealloc(infos);
+ if (res == nullptr) {
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ env->ThrowNew(rt_exception.get(), (name + " extensions not found").c_str());
+ return nullptr;
+ }
+ return res;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1974_ResizeArray(JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jobject ref_gen,
+ jint new_size) {
+ ChangeArraySize change_array_size = reinterpret_cast<ChangeArraySize>(
+ FindExtensionMethod(env, "com.android.art.heap.change_array_size"));
+ if (change_array_size == nullptr) {
+ return;
+ }
+ jmethodID getArr = env->GetMethodID(
+ env->FindClass("java/util/function/Supplier"), "get", "()Ljava/lang/Object;");
+ jobject arr = env->CallObjectMethod(ref_gen, getArr);
+ JvmtiErrorToException(env, jvmti_env, change_array_size(jvmti_env, arr, new_size));
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_art_Test1974_ReadJniRef(JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jlong r) {
+ return env->NewLocalRef(reinterpret_cast<jobject>(static_cast<intptr_t>(r)));
+}
+
+extern "C" JNIEXPORT jlong JNICALL
+Java_art_Test1974_GetWeakGlobalJniRef(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject r) {
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(env->NewWeakGlobalRef(r)));
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1974_GetGlobalJniRef(JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jobject r) {
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(env->NewGlobalRef(r)));
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL
+Java_art_Test1974_GetObjectsWithTag(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) {
+ jsize cnt = 0;
+ jobject* res = nullptr;
+ if (JvmtiErrorToException(
+ env, jvmti_env, jvmti_env->GetObjectsWithTags(1, &tag, &cnt, &res, nullptr))) {
+ return nullptr;
+ }
+ jobjectArray ret = env->NewObjectArray(cnt, env->FindClass("java/lang/Object"), nullptr);
+ if (ret == nullptr) {
+ return nullptr;
+ }
+ for (jsize i = 0; i < cnt; i++) {
+ env->SetObjectArrayElement(ret, i, res[i]);
+ }
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(res));
+ return ret;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1974_runNativeTest(JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jobjectArray arr,
+ jobject resize,
+ jobject print,
+ jobject check) {
+ jmethodID run = env->GetMethodID(env->FindClass("java/lang/Runnable"), "run", "()V");
+ jmethodID accept = env->GetMethodID(
+ env->FindClass("java/util/function/Consumer"), "accept", "(Ljava/lang/Object;)V");
+ env->CallVoidMethod(print, accept, arr);
+ env->CallVoidMethod(resize, run);
+ env->CallVoidMethod(print, accept, arr);
+ env->CallVoidMethod(check, accept, arr);
+}
+} // namespace Test1974ResizeArray
+} // namespace art
diff --git a/test/1974-resize-array/run b/test/1974-resize-array/run
new file mode 100755
index 0000000..96646c8
--- /dev/null
+++ b/test/1974-resize-array/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+./default-run "$@" --jvmti
diff --git a/test/1974-resize-array/src/Main.java b/test/1974-resize-array/src/Main.java
new file mode 100644
index 0000000..3843973
--- /dev/null
+++ b/test/1974-resize-array/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1974.run();
+ }
+}
diff --git a/test/1974-resize-array/src/art/Main.java b/test/1974-resize-array/src/art/Main.java
new file mode 120000
index 0000000..84ae4ac
--- /dev/null
+++ b/test/1974-resize-array/src/art/Main.java
@@ -0,0 +1 @@
+../../../jvmti-common/Main.java
\ No newline at end of file
diff --git a/test/1974-resize-array/src/art/Test1974.java b/test/1974-resize-array/src/art/Test1974.java
new file mode 100644
index 0000000..8460e82
--- /dev/null
+++ b/test/1974-resize-array/src/art/Test1974.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class Test1974 {
+
+ public static final boolean DEBUG = false;
+
+ public static int[] static_field = new int[] {1, 2, 3};
+ public static Object[] static_field_ref = new String[] {"a", "b", "c"};
+
+ public static final class InstanceClass {
+ public int[] instance_field = new int[] {1, 2, 3};
+ public Object[] self_ref;
+
+ public InstanceClass() {
+ self_ref = new Object[] {null, "A", "B", "C"};
+ self_ref[0] = self_ref;
+ }
+ }
+
+ static InstanceClass theInstanceClass;
+ static InstanceClass theOtherInstanceClass;
+
+ static {
+ theInstanceClass = new InstanceClass();
+ theOtherInstanceClass = new InstanceClass();
+ theOtherInstanceClass.instance_field = theInstanceClass.instance_field;
+ theOtherInstanceClass.self_ref = theInstanceClass.self_ref;
+ }
+
+ public static void DbgPrintln(String s) {
+ if (DEBUG) {
+ System.out.println(s);
+ }
+ }
+
+ public interface ThrowRunnable extends Runnable {
+ public default void run() {
+ try {
+ throwRun();
+ } catch (Exception e) {
+ throw new Error("Exception in runner!", e);
+ }
+ }
+
+ public void throwRun() throws Exception;
+ }
+
+ public static void runAsThread(ThrowRunnable r) throws Exception {
+ Thread t = new Thread(r);
+ t.start();
+ t.join();
+ System.out.println("");
+ }
+
+ public static void runInstance() {
+ System.out.println("Test instance");
+ DbgPrintln("Pre hash: " + theInstanceClass.instance_field.hashCode());
+ System.out.println(
+ "val is: " + Arrays.toString(theInstanceClass.instance_field) + " resize +3");
+ ResizeArray(() -> theInstanceClass.instance_field, theInstanceClass.instance_field.length + 5);
+ System.out.println("val is: " + Arrays.toString(theInstanceClass.instance_field));
+ DbgPrintln("Post hash: " + theInstanceClass.instance_field.hashCode());
+ System.out.println(
+ "Same value? " + (theInstanceClass.instance_field == theOtherInstanceClass.instance_field));
+ }
+
+ public static void runHashMap() {
+ System.out.println("Test HashMap");
+ HashMap<byte[], Comparable> map = new HashMap();
+ Comparable the_value = "THE VALUE";
+ Supplier<byte[]> get_the_value =
+ () ->
+ map.entrySet().stream()
+ .filter((x) -> x.getValue().equals(the_value))
+ .findFirst()
+ .get()
+ .getKey();
+ map.put(new byte[] {1, 2, 3, 4}, the_value);
+ map.put(new byte[] {1, 2, 3, 4}, "Other Value");
+ map.put(new byte[] {1, 4}, "Third value");
+ System.out.println("val is: " + Arrays.toString(get_the_value.get()) + " resize +3");
+ System.out.print("Map is: ");
+ map.entrySet().stream()
+ .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
+ .forEach(
+ (e) -> {
+ System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
+ });
+ System.out.println();
+ ResizeArray(get_the_value, 7);
+ System.out.println("val is: " + Arrays.toString(get_the_value.get()));
+ System.out.print("Map is: ");
+ map.entrySet().stream()
+ .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
+ .forEach(
+ (e) -> {
+ System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
+ });
+ System.out.println();
+ }
+
+ public static void runWeakReference() {
+ System.out.println("Test j.l.r.WeakReference");
+ String[] arr = new String[] {"weak", "ref"};
+ WeakReference<String[]> wr = new WeakReference(arr);
+ DbgPrintln("Pre hash: " + wr.get().hashCode());
+ System.out.println("val is: " + Arrays.toString(wr.get()) + " resize +3");
+ ResizeArray(wr::get, wr.get().length + 5);
+ System.out.println("val is: " + Arrays.toString(wr.get()));
+ DbgPrintln("Post hash: " + wr.get().hashCode());
+ System.out.println("Same value? " + (wr.get() == arr));
+ }
+
+ public static void runInstanceSelfRef() {
+ System.out.println("Test instance self-ref");
+ DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+ String pre_to_string = theInstanceClass.self_ref.toString();
+ System.out.println(
+ "val is: "
+ + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
+ + " resize +5 item 0 is "
+ + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+ .replace(pre_to_string, "<SELF REF>"));
+ ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length + 5);
+ System.out.println(
+ "val is: "
+ + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
+ System.out.println(
+ "val is: "
+ + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+ .replace(pre_to_string, "<SELF REF>"));
+ DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+ System.out.println(
+ "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
+ System.out.println(
+ "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
+ System.out.println(
+ "Same inner-structure? "
+ + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
+ }
+
+ public static void runInstanceSelfRefSmall() {
+ System.out.println("Test instance self-ref smaller");
+ DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+ String pre_to_string = theInstanceClass.self_ref.toString();
+ System.out.println(
+ "val is: "
+ + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
+ + " resize -7 item 0 is "
+ + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+ .replace(pre_to_string, "<SELF REF>"));
+ ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length - 7);
+ System.out.println(
+ "val is: "
+ + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
+ System.out.println(
+ "val is: "
+ + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+ .replace(pre_to_string, "<SELF REF>"));
+ DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+ System.out.println(
+ "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
+ System.out.println(
+ "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
+ System.out.println(
+ "Same inner-structure? "
+ + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
+ }
+
+ public static void runLocal() throws Exception {
+ final int[] arr_loc = new int[] {2, 3, 4};
+ int[] arr_loc_2 = arr_loc;
+
+ System.out.println("Test local");
+ DbgPrintln("Pre hash: " + arr_loc.hashCode());
+ System.out.println("val is: " + Arrays.toString(arr_loc) + " resize +5");
+ ResizeArray(() -> arr_loc, arr_loc.length + 5);
+ System.out.println("val is: " + Arrays.toString(arr_loc));
+ DbgPrintln("Post hash: " + arr_loc.hashCode());
+ System.out.println("Same value? " + (arr_loc == arr_loc_2));
+ }
+
+ public static void runLocalSmall() throws Exception {
+ final int[] arr_loc = new int[] {1, 2, 3, 4, 5};
+ int[] arr_loc_2 = arr_loc;
+
+ System.out.println("Test local smaller");
+ DbgPrintln("Pre hash: " + arr_loc.hashCode());
+ System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
+ ResizeArray(() -> arr_loc, arr_loc.length - 2);
+ System.out.println("val is: " + Arrays.toString(arr_loc));
+ DbgPrintln("Post hash: " + arr_loc.hashCode());
+ System.out.println("Same value? " + (arr_loc == arr_loc_2));
+ }
+
+ public static void runMultiThreadLocal() throws Exception {
+ final CountDownLatch cdl = new CountDownLatch(1);
+ final CountDownLatch start_cdl = new CountDownLatch(2);
+ final Supplier<Object[]> getArr =
+ new Supplier<Object[]>() {
+ public final Object[] arr = new Object[] {"1", "2", "3"};
+
+ public Object[] get() {
+ return arr;
+ }
+ };
+ final ArrayList<String> msg1 = new ArrayList();
+ final ArrayList<String> msg2 = new ArrayList();
+ final Consumer<String> print1 =
+ (String s) -> {
+ msg1.add(s);
+ };
+ final Consumer<String> print2 =
+ (String s) -> {
+ msg2.add(s);
+ };
+ Function<Consumer<String>, Runnable> r =
+ (final Consumer<String> c) ->
+ () -> {
+ c.accept("Test local multi-thread");
+ Object[] arr_loc = getArr.get();
+ Object[] arr_loc_2 = getArr.get();
+
+ DbgPrintln("Pre hash: " + arr_loc.hashCode());
+ c.accept("val is: " + Arrays.toString(arr_loc) + " resize -2");
+
+ try {
+ start_cdl.countDown();
+ cdl.await();
+ } catch (Exception e) {
+ throw new Error("failed await", e);
+ }
+ c.accept("val is: " + Arrays.toString(arr_loc));
+ DbgPrintln("Post hash: " + arr_loc.hashCode());
+ c.accept("Same value? " + (arr_loc == arr_loc_2));
+ };
+ Thread t1 = new Thread(r.apply(print1));
+ Thread t2 = new Thread(r.apply(print2));
+ t1.start();
+ t2.start();
+ start_cdl.await();
+ ResizeArray(getArr, 1);
+ cdl.countDown();
+ t1.join();
+ t2.join();
+ for (String s : msg1) {
+ System.out.println("T1: " + s);
+ }
+ for (String s : msg2) {
+ System.out.println("T2: " + s);
+ }
+ }
+
+ public static void runWithLocks() throws Exception {
+ final CountDownLatch cdl = new CountDownLatch(1);
+ final CountDownLatch start_cdl = new CountDownLatch(2);
+ final CountDownLatch waiter_start_cdl = new CountDownLatch(1);
+ final Supplier<Object[]> getArr =
+ new Supplier<Object[]>() {
+ public final Object[] arr = new Object[] {"A", "2", "C"};
+
+ public Object[] get() {
+ return arr;
+ }
+ };
+ // basic order of operations noted above each line.
+ // Waiter runs to the 'wait' then t1 runs to the cdl.await, then current thread runs.
+ Runnable r =
+ () -> {
+ System.out.println("Test locks");
+ Object[] arr_loc = getArr.get();
+ Object[] arr_loc_2 = getArr.get();
+
+ DbgPrintln("Pre hash: " + arr_loc.hashCode());
+ System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
+
+ try {
+ // OP 1
+ waiter_start_cdl.await();
+ // OP 6
+ synchronized (arr_loc) {
+ // OP 7
+ synchronized (arr_loc_2) {
+ // OP 8
+ start_cdl.countDown();
+ // OP 9
+ cdl.await();
+ // OP 13
+ }
+ }
+ } catch (Exception e) {
+ throw new Error("failed await", e);
+ }
+ System.out.println("val is: " + Arrays.toString(arr_loc));
+ DbgPrintln("Post hash: " + arr_loc.hashCode());
+ System.out.println("Same value? " + (arr_loc == arr_loc_2));
+ };
+ Thread t1 = new Thread(r);
+ Thread waiter =
+ new Thread(
+ () -> {
+ try {
+ Object a = getArr.get();
+ // OP 2
+ synchronized (a) {
+ // OP 3
+ waiter_start_cdl.countDown();
+ // OP 4
+ start_cdl.countDown();
+ // OP 5
+ a.wait();
+ // OP 15
+ }
+ } catch (Exception e) {
+ throw new Error("Failed wait!", e);
+ }
+ });
+ waiter.start();
+ t1.start();
+ // OP 10
+ start_cdl.await();
+ // OP 11
+ ResizeArray(getArr, 1);
+ // OP 12
+ cdl.countDown();
+ // OP 14
+ synchronized (getArr.get()) {
+ // Make sure thread wakes up and has the right lock.
+ getArr.get().notifyAll();
+ }
+ waiter.join();
+ t1.join();
+ // Make sure other threads can still lock it.
+ synchronized (getArr.get()) {
+ }
+ System.out.println("Locks seem to all work.");
+ }
+
+ public static void runWithJniGlobal() throws Exception {
+ Object[] arr = new Object[] {"1", "11", "111"};
+ final long globalID = GetGlobalJniRef(arr);
+ System.out.println("Test jni-ref");
+ DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
+ System.out.println(
+ "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
+ ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
+ System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
+ DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
+ System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
+ }
+
+ public static void runWithJniWeakGlobal() throws Exception {
+ Object[] arr = new Object[] {"2", "22", "222"};
+ final long globalID = GetWeakGlobalJniRef(arr);
+ System.out.println("Test weak jni-ref");
+ DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
+ System.out.println(
+ "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
+ ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
+ System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
+ DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
+ System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
+ if (ReadJniRef(globalID) != arr) {
+ throw new Error("Didn't update weak global!");
+ }
+ }
+
+ public static void runWithJniLocals() throws Exception {
+ final Object[] arr = new Object[] {"3", "32", "322"};
+ System.out.println("Test jni local ref");
+ Consumer<Object> checker = (o) -> System.out.println("Same value? " + (o == arr));
+ Consumer<Object> printer =
+ (o) -> System.out.println("val is: " + Arrays.toString((Object[]) o));
+ Runnable resize =
+ () -> {
+ System.out.println("Resize +4");
+ ResizeArray(() -> arr, arr.length + 4);
+ };
+ runNativeTest(arr, resize, printer, checker);
+ }
+
+ public static native void runNativeTest(
+ Object[] arr, Runnable resize, Consumer<Object> printer, Consumer<Object> checker);
+
+ public static void runWithJvmtiTags() throws Exception {
+ Object[] arr = new Object[] {"3", "33", "333"};
+ long globalID = 333_333_333l;
+ Main.setTag(arr, globalID);
+ System.out.println("Test jvmti-tags");
+ DbgPrintln("Pre hash: " + arr.hashCode());
+ System.out.println(
+ "val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)) + " resize +5");
+ ResizeArray(() -> arr, arr.length + 5);
+ Object[] after_tagged_obj = GetObjectsWithTag(globalID);
+ System.out.println("val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)));
+ DbgPrintln("Post hash: " + after_tagged_obj[0].hashCode());
+ System.out.println("Same value? " + (after_tagged_obj[0] == arr));
+ }
+
+ public static void run() throws Exception {
+ // Simple
+ runAsThread(Test1974::runInstance);
+
+ // HashMap
+ runAsThread(Test1974::runHashMap);
+
+ // j.l.ref.WeakReference
+ runAsThread(Test1974::runWeakReference);
+
+ // Self-referential arrays.
+ runAsThread(Test1974::runInstanceSelfRef);
+ runAsThread(Test1974::runInstanceSelfRefSmall);
+
+ // Local variables simple
+ runAsThread(Test1974::runLocal);
+ runAsThread(Test1974::runLocalSmall);
+
+ // multiple threads local variables
+ runAsThread(Test1974::runMultiThreadLocal);
+
+ // using as monitors and waiting
+ runAsThread(Test1974::runWithLocks);
+
+ // Basic jni global refs
+ runAsThread(Test1974::runWithJniGlobal);
+
+ // Basic jni weak global refs
+ runAsThread(Test1974::runWithJniWeakGlobal);
+
+ // Basic JNI local refs
+ runAsThread(Test1974::runWithJniLocals);
+
+ // Basic jvmti tags
+ runAsThread(Test1974::runWithJvmtiTags);
+ }
+
+ // Use a supplier so that we don't have to have a local ref to the resized
+ // array if we don't want it
+ public static native <T> void ResizeArray(Supplier<T> arr, int new_size);
+
+ public static native <T> long GetGlobalJniRef(T t);
+
+ public static native <T> long GetWeakGlobalJniRef(T t);
+
+ public static native <T> T ReadJniRef(long t);
+
+ public static native Object[] GetObjectsWithTag(long tag);
+}