diff options
| author | 2019-07-18 16:08:41 -0700 | |
|---|---|---|
| committer | 2019-08-01 17:50:27 +0000 | |
| commit | c14ec8facf0ea51e7531fa0acbed3410468b0356 (patch) | |
| tree | a70bc3cffcaf763e4951c96918bc6f8b25a472b3 | |
| parent | 147a911636402f2658ce60bd3ad62eb197b00f74 (diff) | |
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
| -rw-r--r-- | openjdkjvmti/ti_extension.cc | 23 | ||||
| -rw-r--r-- | openjdkjvmti/ti_heap.cc | 280 | ||||
| -rw-r--r-- | openjdkjvmti/ti_heap.h | 2 | ||||
| -rw-r--r-- | runtime/thread_list.h | 7 | ||||
| -rw-r--r-- | test/1974-resize-array/expected.txt | 78 | ||||
| -rw-r--r-- | test/1974-resize-array/info.txt | 3 | ||||
| -rw-r--r-- | test/1974-resize-array/resize_array.cc | 149 | ||||
| -rwxr-xr-x | test/1974-resize-array/run | 18 | ||||
| -rw-r--r-- | test/1974-resize-array/src/Main.java | 21 | ||||
| l--------- | test/1974-resize-array/src/art/Main.java | 1 | ||||
| -rw-r--r-- | test/1974-resize-array/src/art/Test1974.java | 473 | ||||
| -rw-r--r-- | test/Android.bp | 1 | ||||
| -rw-r--r-- | test/knownfailures.json | 3 |
13 files changed, 1055 insertions, 4 deletions
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc index a21a97f5d0..9d31a934f3 100644 --- a/openjdkjvmti/ti_extension.cc +++ b/openjdkjvmti/ti_extension.cc @@ -22,7 +22,6 @@ * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any @@ -370,6 +369,28 @@ jvmtiError ExtensionUtil::GetExtensionFunctions(jvmtiEnv* env, return error; } + // ChangeArraySize + error = add_extension( + reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::ChangeArraySize), + "com.android.art.heap.change_array_size", + "Changes the size of a java array. As far as all JNI and java code is concerned this is" + " atomic. Must have can_tag_objects capability. If the new length of the array is smaller" + " than the original length, then the array will be truncated to the new length. Otherwise," + " all new slots will be filled with null, 0, or False as appropriate for the array type.", + { + { "array", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false }, + { "new_size", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false }, + }, + { + ERR(NULL_POINTER), + ERR(MUST_POSSESS_CAPABILITY), + ERR(ILLEGAL_ARGUMENT), + ERR(OUT_OF_MEMORY), + }); + if (error != ERR(NONE)) { + return error; + } + // Copy into output buffer. *extension_count_ptr = ext_vector.size(); diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc index 898363a269..3c2b82e9bc 100644 --- a/openjdkjvmti/ti_heap.cc +++ b/openjdkjvmti/ti_heap.cc @@ -16,33 +16,53 @@ #include "ti_heap.h" +#include <ios> + +#include "android-base/logging.h" +#include "android-base/thread_annotations.h" +#include "arch/context.h" #include "art_field-inl.h" #include "art_jvmti.h" #include "base/macros.h" #include "base/mutex.h" +#include "base/utils.h" #include "class_linker.h" #include "class_root.h" +#include "deopt_manager.h" #include "dex/primitive.h" +#include "gc/collector_type.h" +#include "gc/gc_cause.h" #include "gc/heap-visit-objects-inl.h" -#include "gc/heap.h" +#include "gc/heap-inl.h" +#include "gc/scoped_gc_critical_section.h" #include "gc_root-inl.h" +#include "handle.h" #include "handle_scope.h" #include "java_frame_root_info.h" #include "jni/jni_env_ext.h" #include "jni/jni_id_manager.h" #include "jni/jni_internal.h" #include "jvmti_weak_table-inl.h" +#include "mirror/array-inl.h" +#include "mirror/array.h" #include "mirror/class.h" #include "mirror/object-inl.h" +#include "mirror/object-refvisitor-inl.h" #include "mirror/object_array-inl.h" +#include "mirror/object_array-alloc-inl.h" +#include "mirror/object_reference.h" #include "obj_ptr-inl.h" +#include "object_callbacks.h" #include "object_tagging.h" +#include "offsets.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" -#include "scoped_thread_state_change.h" #include "stack.h" #include "thread-inl.h" #include "thread_list.h" +#include "ti_logging.h" +#include "ti_stack.h" +#include "ti_thread.h" #include "well_known_classes.h" namespace openjdkjvmti { @@ -1591,4 +1611,260 @@ jvmtiError HeapExtensions::IterateThroughHeapExt(jvmtiEnv* env, user_data); } +namespace { + +using ArrayPtr = art::ObjPtr<art::mirror::Array>; + +static void ReplaceObjectReferences(ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr) + REQUIRES(art::Locks::mutator_lock_, + art::Locks::user_code_suspension_lock_, + art::Roles::uninterruptible_) { + art::Runtime::Current()->GetHeap()->VisitObjectsPaused( + [&](art::mirror::Object* ref) REQUIRES_SHARED(art::Locks::mutator_lock_) { + // Rewrite all references in the object if needed. + class ResizeReferenceVisitor { + public: + using CompressedObj = art::mirror::CompressedReference<art::mirror::Object>; + ResizeReferenceVisitor(ArrayPtr old_arr, ArrayPtr new_arr) + : old_arr_(old_arr), new_arr_(new_arr) {} + + // Ignore class roots. These do not need to be handled for arrays. + void VisitRootIfNonNull(CompressedObj* root ATTRIBUTE_UNUSED) const {} + void VisitRoot(CompressedObj* root ATTRIBUTE_UNUSED) const {} + + void operator()(art::ObjPtr<art::mirror::Object> obj, + art::MemberOffset off, + bool is_static ATTRIBUTE_UNUSED) const + REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (obj->GetFieldObject<art::mirror::Object>(off) == old_arr_) { + LOG(DEBUG) << "Updating field at offset " << off.Uint32Value() << " of type " + << obj->GetClass()->PrettyClass(); + obj->SetFieldObject</*transaction*/ false>(off, new_arr_); + } + } + + // java.lang.ref.Reference visitor. + void operator()(art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED, + art::ObjPtr<art::mirror::Reference> ref) const + REQUIRES_SHARED(art::Locks::mutator_lock_) { + operator()(ref, art::mirror::Reference::ReferentOffset(), /* is_static */ false); + } + + private: + ArrayPtr old_arr_; + ArrayPtr new_arr_; + }; + + ResizeReferenceVisitor rrv(old_arr_ptr, new_arr_ptr); + ref->VisitReferences(rrv, rrv); + }); +} + +static void ReplaceStrongRoots(art::Thread* self, ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr) + REQUIRES(art::Locks::mutator_lock_, + art::Locks::user_code_suspension_lock_, + art::Roles::uninterruptible_) { + // replace root references expcept java frames. + struct ResizeRootVisitor : public art::RootVisitor { + public: + ResizeRootVisitor(ArrayPtr new_val, ArrayPtr old_val) + : new_val_(new_val), old_val_(old_val) {} + + // TODO It's somewhat annoying to have to have this function implemented twice. It might be + // good/useful to implement operator= for CompressedReference to allow us to use a template to + // implement both of these. + void VisitRoots(art::mirror::Object*** roots, size_t count, const art::RootInfo& info) override + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::mirror::Object*** end = roots + count; + for (art::mirror::Object** obj = *roots; roots != end; obj = *(++roots)) { + if (*obj == old_val_) { + // Java frames might have the JIT doing optimizations (for example loop-unrolling or + // eliding bounds checks) so we need deopt them once we're done here. + if (info.GetType() == art::RootType::kRootJavaFrame) { + threads_with_roots_.insert(info.GetThreadId()); + } + *obj = new_val_.Ptr(); + } + } + } + + void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots, + size_t count, + const art::RootInfo& info) override REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::mirror::CompressedReference<art::mirror::Object>** end = roots + count; + for (art::mirror::CompressedReference<art::mirror::Object>* obj = *roots; roots != end; + obj = *(++roots)) { + if (obj->AsMirrorPtr() == old_val_) { + // Java frames might have the JIT doing optimizations (for example loop-unrolling or + // eliding bounds checks) so we need deopt them once we're done here. + if (info.GetType() == art::RootType::kRootJavaFrame) { + threads_with_roots_.insert(info.GetThreadId()); + } + obj->Assign(new_val_); + } + } + } + + const std::unordered_set<uint32_t>& GetThreadsWithJavaFrameRoots() const { + return threads_with_roots_; + } + + private: + ArrayPtr new_val_; + ArrayPtr old_val_; + std::unordered_set<uint32_t> threads_with_roots_; + }; + ResizeRootVisitor rrv(new_arr_ptr, old_arr_ptr); + art::Runtime::Current()->VisitRoots(&rrv, art::VisitRootFlags::kVisitRootFlagAllRoots); + // Handle java Frames. Annoyingly the JIT can embed information about the length of the array into + // the compiled code. By changing the length of the array we potentially invalidate these + // assumptions and so could cause (eg) OOB array access or other issues. + if (!rrv.GetThreadsWithJavaFrameRoots().empty()) { + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + art::ThreadList* thread_list = art::Runtime::Current()->GetThreadList(); + art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation(); + for (uint32_t id : rrv.GetThreadsWithJavaFrameRoots()) { + art::Thread* t = thread_list->FindThreadByThreadId(id); + CHECK(t != nullptr) << "id " << id << " does not refer to a valid thread." + << " Where did the roots come from?"; + // TODO Use deopt manager. We need a version that doesn't acquire all the locks we + // already have. + // TODO We technically only need to do this if the frames are not already being interpreted. + // The cost for doing an extra stack walk is unlikely to be worth it though. + instr->InstrumentThreadStack(t); + } + } +} + +static void ReplaceWeakRoots(ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr) + REQUIRES(art::Locks::mutator_lock_, + art::Locks::user_code_suspension_lock_, + art::Roles::uninterruptible_) { + struct ReplaceWeaksVisitor : public art::IsMarkedVisitor { + public: + ReplaceWeaksVisitor(ArrayPtr old_arr, ArrayPtr new_arr) + : old_arr_(old_arr), new_arr_(new_arr) {} + + art::mirror::Object* IsMarked(art::mirror::Object* obj) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (obj == old_arr_) { + return new_arr_.Ptr(); + } else { + return obj; + } + } + + private: + ArrayPtr old_arr_; + ArrayPtr new_arr_; + }; + ReplaceWeaksVisitor rwv(old_arr_ptr, new_arr_ptr); + art::Runtime::Current()->SweepSystemWeaks(&rwv); +} + +static void PerformArrayReferenceReplacement(art::Thread* self, + ArrayPtr old_arr_ptr, + ArrayPtr new_arr_ptr) + REQUIRES(art::Locks::mutator_lock_, + art::Locks::user_code_suspension_lock_, + art::Roles::uninterruptible_) { + ReplaceObjectReferences(old_arr_ptr, new_arr_ptr); + ReplaceStrongRoots(self, old_arr_ptr, new_arr_ptr); + ReplaceWeakRoots(old_arr_ptr, new_arr_ptr); +} + +} // namespace + +jvmtiError HeapExtensions::ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size) { + if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.can_tag_objects != 1) { + return ERR(MUST_POSSESS_CAPABILITY); + } + art::Thread* self = art::Thread::Current(); + ScopedNoUserCodeSuspension snucs(self); + art::ScopedObjectAccess soa(self); + if (arr == nullptr) { + JVMTI_LOG(INFO, env) << "Cannot resize a null object"; + return ERR(NULL_POINTER); + } + art::ObjPtr<art::mirror::Class> klass(soa.Decode<art::mirror::Object>(arr)->GetClass()); + if (!klass->IsArrayClass()) { + JVMTI_LOG(INFO, env) << klass->PrettyClass() << " is not an array class!"; + return ERR(ILLEGAL_ARGUMENT); + } + if (new_size < 0) { + JVMTI_LOG(INFO, env) << "Cannot resize an array to a negative size"; + return ERR(ILLEGAL_ARGUMENT); + } + // Allocate the new copy. + art::StackHandleScope<2> hs(self); + art::Handle<art::mirror::Array> old_arr(hs.NewHandle(soa.Decode<art::mirror::Array>(arr))); + art::MutableHandle<art::mirror::Array> new_arr(hs.NewHandle<art::mirror::Array>(nullptr)); + if (klass->IsObjectArrayClass()) { + new_arr.Assign( + art::mirror::ObjectArray<art::mirror::Object>::Alloc(self, old_arr->GetClass(), new_size)); + } else { + // NB This also copies the old array but since we aren't suspended we need to do this again to + // catch any concurrent modifications. + new_arr.Assign(art::mirror::Array::CopyOf(old_arr, self, new_size)); + } + if (new_arr.IsNull()) { + self->AssertPendingOOMException(); + JVMTI_LOG(INFO, env) << "Unable to allocate " << old_arr->GetClass()->PrettyClass() + << " (length: " << new_size << ") due to OOME. Error was: " + << self->GetException()->Dump(); + self->ClearException(); + return ERR(OUT_OF_MEMORY); + } else { + self->AssertNoPendingException(); + } + // Suspend everything. + art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended); + art::gc::ScopedGCCriticalSection sgccs( + self, art::gc::GcCause::kGcCauseDebugger, art::gc::CollectorType::kCollectorTypeDebugger); + art::ScopedSuspendAll ssa("Resize array!"); + // Replace internals. + new_arr->SetLockWord(old_arr->GetLockWord(false), false); + old_arr->SetLockWord(art::LockWord::Default(), false); + // Copy the contents now when everything is suspended. + int32_t size = std::min(old_arr->GetLength(), new_size); + switch (old_arr->GetClass()->GetComponentType()->GetPrimitiveType()) { + case art::Primitive::kPrimBoolean: + new_arr->AsBooleanArray()->Memcpy(0, old_arr->AsBooleanArray(), 0, size); + break; + case art::Primitive::kPrimByte: + new_arr->AsByteArray()->Memcpy(0, old_arr->AsByteArray(), 0, size); + break; + case art::Primitive::kPrimChar: + new_arr->AsCharArray()->Memcpy(0, old_arr->AsCharArray(), 0, size); + break; + case art::Primitive::kPrimShort: + new_arr->AsShortArray()->Memcpy(0, old_arr->AsShortArray(), 0, size); + break; + case art::Primitive::kPrimInt: + new_arr->AsIntArray()->Memcpy(0, old_arr->AsIntArray(), 0, size); + break; + case art::Primitive::kPrimLong: + new_arr->AsLongArray()->Memcpy(0, old_arr->AsLongArray(), 0, size); + break; + case art::Primitive::kPrimFloat: + new_arr->AsFloatArray()->Memcpy(0, old_arr->AsFloatArray(), 0, size); + break; + case art::Primitive::kPrimDouble: + new_arr->AsDoubleArray()->Memcpy(0, old_arr->AsDoubleArray(), 0, size); + break; + case art::Primitive::kPrimNot: + for (int32_t i = 0; i < size; i++) { + new_arr->AsObjectArray<art::mirror::Object>()->Set( + i, old_arr->AsObjectArray<art::mirror::Object>()->Get(i)); + } + break; + case art::Primitive::kPrimVoid: + LOG(FATAL) << "void-array is not a legal type!"; + UNREACHABLE(); + } + // Actually replace all the pointers. + PerformArrayReferenceReplacement(self, old_arr.Get(), new_arr.Get()); + return OK; +} + } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h index 382d80f576..5a32436aa5 100644 --- a/openjdkjvmti/ti_heap.h +++ b/openjdkjvmti/ti_heap.h @@ -72,6 +72,8 @@ class HeapExtensions { jclass klass, const jvmtiHeapCallbacks* callbacks, const void* user_data); + + static jvmtiError JNICALL ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size); }; } // namespace openjdkjvmti diff --git a/runtime/thread_list.h b/runtime/thread_list.h index 93b9bdf43b..b5b4450498 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -148,6 +148,13 @@ class ThreadList { void ForEach(void (*callback)(Thread*, void*), void* context) REQUIRES(Locks::thread_list_lock_); + template<typename CallBack> + void ForEach(CallBack cb) REQUIRES(Locks::thread_list_lock_) { + ForEach([](Thread* t, void* ctx) REQUIRES(Locks::thread_list_lock_) { + (*reinterpret_cast<CallBack*>(ctx))(t); + }, &cb); + } + // Add/remove current thread from list. void Register(Thread* self) REQUIRES(Locks::runtime_shutdown_lock_) diff --git a/test/1974-resize-array/expected.txt b/test/1974-resize-array/expected.txt new file mode 100644 index 0000000000..4b0d432f77 --- /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 0000000000..ef4fa40522 --- /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 0000000000..dadcbeae22 --- /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 0000000000..96646c8a2e --- /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 0000000000..38439731f6 --- /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 0000000000..84ae4ac310 --- /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 0000000000..8460e827eb --- /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); +} diff --git a/test/Android.bp b/test/Android.bp index baf5e227d6..2254477dd1 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -300,6 +300,7 @@ art_cc_defaults { "1968-force-early-return/force_early_return.cc", "1969-force-early-return-void/force_early_return_void.cc", "1970-force-early-return-long/force_early_return_long.cc", + "1974-resize-array/resize_array.cc", ], // Use NDK-compatible headers for ctstiagent. header_libs: [ diff --git a/test/knownfailures.json b/test/knownfailures.json index 2a64ba6f48..1d6e36d43a 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -1117,7 +1117,8 @@ "1951-monitor-enter-no-suspend", "1957-error-ext", "1972-jni-id-swap-indices", - "1973-jni-id-swap-pointer" + "1973-jni-id-swap-pointer", + "1974-resize-array" ], "variant": "jvm", "description": ["Doesn't run on RI."] |