Revert^2 "Allow structural redefinition on non-final classes."
We were incorrectly racing with the rest of the runtime in a couple of
places. First we would return an ObjPtr of a newly defined class after
holding it over a suspend point. This could lead to DefineClass
returning an obsolete class in some cases.
We also failed to ensure the class-status was synchronized between the
old and new classes during structural redefinition. This could lead to
a class appearing to go backwards in status.
This reverts commit 88b1c83080afcb2bfb6f781ded1c90fe8f9eab4d.
Reason for revert: Fixed issues causing test failures.
Test: ./test.py --host
Bug: 134162467
Bug: 144168550
Change-Id: I4d0f7718490532f0ef14a9561b8e7000ef292b12
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index 842716f..49ac2f5 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -76,7 +76,7 @@
" f0: f1bc 0f00 cmp.w ip, #0\n",
" f4: bf18 it ne\n",
" f6: f20d 4c01 addwne ip, sp, #1025 ; 0x401\n",
- " fa: f8d9 c09c ldr.w ip, [r9, #156] ; 0x9c\n",
+ " fa: f8d9 c0a4 ldr.w ip, [r9, #164] ; 0xa4\n",
" fe: f1bc 0f00 cmp.w ip, #0\n",
" 102: d171 bne.n 1e8 <VixlJniHelpers+0x1e8>\n",
" 104: f8cd c7ff str.w ip, [sp, #2047] ; 0x7ff\n",
@@ -153,7 +153,7 @@
" 21c: f8d9 8034 ldr.w r8, [r9, #52] ; 0x34\n",
" 220: 4770 bx lr\n",
" 222: 4660 mov r0, ip\n",
- " 224: f8d9 c2e4 ldr.w ip, [r9, #740] ; 0x2e4\n",
+ " 224: f8d9 c2ec ldr.w ip, [r9, #748] ; 0x2ec\n",
" 228: 47e0 blx ip\n",
nullptr
};
diff --git a/libartbase/base/iteration_range.h b/libartbase/base/iteration_range.h
index cd87d85..eaed8b0 100644
--- a/libartbase/base/iteration_range.h
+++ b/libartbase/base/iteration_range.h
@@ -49,6 +49,11 @@
return IterationRange<Iter>(begin_it, end_it);
}
+template<typename List>
+inline IterationRange<typename List::iterator> MakeIterationRange(List& list) {
+ return IterationRange<typename List::iterator>(list.begin(), list.end());
+}
+
template <typename Iter>
inline IterationRange<Iter> MakeEmptyIterationRange(const Iter& it) {
return IterationRange<Iter>(it, it);
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 058a188..1301697 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -423,29 +423,31 @@
reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClasses),
"com.android.art.class.structurally_redefine_classes",
"Entrypoint for structural class redefinition. Has the same signature as RedefineClasses."
- " Currently this only supports adding new static fields to a class without any instance"
- " fields or methods. After calling this com.android.art.structural_dex_file_load_hook"
- " events will be triggered, followed by re-transformable ClassFileLoadHook events. After"
- " this method completes subsequent RetransformClasses calls will use the input to this"
- " function as the initial class definition.",
+ " Currently does not support redefining a class and any of its supertypes at the same time."
+ " Only supports additive changes, methods and fields may not be removed. Supertypes and"
+ " implemented interfaces may not be changed. After calling this"
+ " com.android.art.structural_dex_file_load_hook events will be triggered, followed by"
+ " re-transformable ClassFileLoadHook events. After this method completes subsequent"
+ " RetransformClasses calls will use the input to this function as the initial class"
+ " definition.",
{
- { "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
- { "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false },
+ { "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+ { "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false },
},
{
- ERR(CLASS_LOADER_UNSUPPORTED),
- ERR(FAILS_VERIFICATION),
- ERR(ILLEGAL_ARGUMENT),
- ERR(INVALID_CLASS),
- ERR(MUST_POSSESS_CAPABILITY),
- ERR(MUST_POSSESS_CAPABILITY),
- ERR(NULL_POINTER),
- ERR(OUT_OF_MEMORY),
- ERR(UNMODIFIABLE_CLASS),
- ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
- ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
- ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
- ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+ ERR(CLASS_LOADER_UNSUPPORTED),
+ ERR(FAILS_VERIFICATION),
+ ERR(ILLEGAL_ARGUMENT),
+ ERR(INVALID_CLASS),
+ ERR(MUST_POSSESS_CAPABILITY),
+ ERR(MUST_POSSESS_CAPABILITY),
+ ERR(NULL_POINTER),
+ ERR(OUT_OF_MEMORY),
+ ERR(UNMODIFIABLE_CLASS),
+ ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
+ ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
+ ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
+ ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
});
if (error != ERR(NONE)) {
return error;
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
index b25b4d1..e1f349a 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -1653,6 +1653,10 @@
// We don't want to update the declaring class of any objects. They will be replaced
// in the heap and we need the declaring class to know its size.
return;
+ } else if (UNLIKELY(!is_static && off == art::mirror::Class::SuperClassOffset() &&
+ obj->IsClass())) {
+ // We don't want to be messing with the class hierarcy either.
+ return;
}
VLOG(plugin) << "Updating field at offset " << off.Uint32Value() << " of type "
<< obj->GetClass()->PrettyClass();
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 730894c..92ca7d6 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -54,7 +54,10 @@
#include "base/casts.h"
#include "base/enums.h"
#include "base/globals.h"
+#include "base/iteration_range.h"
#include "base/length_prefixed_array.h"
+#include "base/locks.h"
+#include "base/stl_util.h"
#include "base/utils.h"
#include "class_linker-inl.h"
#include "class_linker.h"
@@ -461,36 +464,6 @@
"safe to structurally redefine it.";
return ERR(UNMODIFIABLE_CLASS);
}
- // Check for already existing non-static fields/methods.
- // TODO Remove this once we support generic method/field addition.
- if (!klass->IsFinal()) {
- bool non_static_method = false;
- klass->VisitMethods([&](art::ArtMethod* m) REQUIRES_SHARED(art::Locks::mutator_lock_) {
- // Since direct-methods (ie privates + <init> are not in any vtable/iftable we can update
- // them).
- if (!m->IsDirect()) {
- non_static_method = true;
- *error_msg = StringPrintf("%s has a non-direct function %s",
- klass->PrettyClass().c_str(),
- m->PrettyMethod().c_str());
- }
- }, art::kRuntimePointerSize);
- if (non_static_method) {
- return ERR(UNMODIFIABLE_CLASS);
- }
- bool non_static_field = false;
- klass->VisitFields([&](art::ArtField* f) REQUIRES_SHARED(art::Locks::mutator_lock_) {
- if (!f->IsStatic()) {
- non_static_field = true;
- *error_msg = StringPrintf("%s has a non-static field %s",
- klass->PrettyClass().c_str(),
- f->PrettyField().c_str());
- }
- });
- if (non_static_field) {
- return ERR(UNMODIFIABLE_CLASS);
- }
- }
// Check for fields/methods which were returned before moving to index jni id type.
// TODO We might want to rework how this is done. Once full redefinition is implemented we will
// need to check any subtypes too.
@@ -974,8 +947,7 @@
RecordHasVirtualMembers();
}
if (old_iter == old_methods.cend()) {
- // TODO Support adding non-static methods.
- if (is_structural && (new_method.IsStaticOrDirect() || h_klass->IsFinal())) {
+ if (is_structural) {
RecordNewMethodAdded();
} else {
RecordFailure(
@@ -1038,8 +1010,7 @@
RecordHasVirtualMembers();
}
if (old_iter == old_fields.cend()) {
- // TODO Support adding non-static fields.
- if (driver_->IsStructuralRedefinition() && (new_field.IsStatic() || h_klass->IsFinal())) {
+ if (driver_->IsStructuralRedefinition()) {
RecordNewFieldAdded();
} else {
RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
@@ -1198,9 +1169,11 @@
kSlotNewClassObject = 8,
kSlotOldInstanceObjects = 9,
kSlotNewInstanceObjects = 10,
+ kSlotOldClasses = 11,
+ kSlotNewClasses = 12,
// Must be last one.
- kNumSlots = 11,
+ kNumSlots = 13,
};
// This needs to have a HandleScope passed in that is capable of creating a new Handle without
@@ -1277,6 +1250,16 @@
return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>>::DownCast(
GetSlot(klass_index, kSlotNewInstanceObjects));
}
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetOldClasses(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>>::DownCast(
+ GetSlot(klass_index, kSlotOldClasses));
+ }
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetNewClasses(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>>::DownCast(
+ GetSlot(klass_index, kSlotNewClasses));
+ }
void SetSourceClassLoader(jint klass_index, art::ObjPtr<art::mirror::ClassLoader> loader)
REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -1327,6 +1310,16 @@
REQUIRES_SHARED(art::Locks::mutator_lock_) {
SetSlot(klass_index, kSlotNewInstanceObjects, objs);
}
+ void SetOldClasses(jint klass_index,
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotOldClasses, klasses);
+ }
+ void SetNewClasses(jint klass_index,
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotNewClasses, klasses);
+ }
int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
return arr_->GetLength() / kNumSlots;
}
@@ -1470,6 +1463,14 @@
REQUIRES_SHARED(art::Locks::mutator_lock_) {
return holder_.GetNewInstanceObjects(idx_);
}
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetOldClasses() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetOldClasses(idx_);
+ }
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetNewClasses() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetNewClasses(idx_);
+ }
int32_t GetIndex() const {
return idx_;
}
@@ -1518,6 +1519,14 @@
REQUIRES_SHARED(art::Locks::mutator_lock_) {
holder_.SetNewInstanceObjects(idx_, objs);
}
+ void SetOldClasses(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetOldClasses(idx_, klasses);
+ }
+ void SetNewClasses(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetNewClasses(idx_, klasses);
+ }
private:
int32_t idx_;
@@ -1628,23 +1637,104 @@
art::VariableSizedHandleScope hs(driver_->self_);
art::Handle<art::mirror::Class> old_klass(hs.NewHandle(cur_data->GetMirrorClass()));
std::vector<art::Handle<art::mirror::Object>> old_instances;
+ std::vector<art::Handle<art::mirror::Class>> old_types;
art::gc::Heap* heap = driver_->runtime_->GetHeap();
+ auto is_subtype = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // We've already waited for class defines to be finished and paused them. All classes should be
+ // either resolved or error. We don't need to do anything with error classes, since they cannot
+ // be accessed in any observable way.
+ return obj->IsClass() && obj->AsClass()->IsResolved() &&
+ old_klass->IsAssignableFrom(obj->AsClass());
+ };
auto is_instance = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
- if (HasVirtualMembers()) {
- return old_klass->IsAssignableFrom(obj->GetClass());
- } else {
- // We don't need to deal with objects of subtypes when we don't modify virtuals since the
- // vtable + field layout will remain the same.
- return old_klass.Get() == obj->GetClass();
- }
+ return obj->InstanceOf(old_klass.Get());
};
heap->VisitObjects([&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
if (is_instance(obj)) {
- CHECK(old_klass.Get() == obj->GetClass()) << "No support for subtypes yet!";
old_instances.push_back(hs.NewHandle(obj));
+ } else if (is_subtype(obj)) {
+ old_types.push_back(hs.NewHandle(obj->AsClass()));
}
});
VLOG(plugin) << "Collected " << old_instances.size() << " instances to recreate!";
+ VLOG(plugin) << "Found " << old_types.size() << " types that are/are subtypes of "
+ << old_klass->PrettyClass();
+
+ art::Handle<art::mirror::Class> cls_array_class(
+ hs.NewHandle(art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Class>>(
+ driver_->runtime_->GetClassLinker())));
+ art::Handle<art::mirror::ObjectArray<art::mirror::Class>> old_classes_arr(
+ hs.NewHandle(art::mirror::ObjectArray<art::mirror::Class>::Alloc(
+ driver_->self_, cls_array_class.Get(), old_types.size())));
+ if (old_classes_arr.IsNull()) {
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate old_classes arrays!");
+ return false;
+ }
+ // Sort the old_types topologically.
+ {
+ art::ScopedAssertNoThreadSuspension sants("Sort classes");
+ auto count_distance =
+ [&](art::ObjPtr<art::mirror::Class> c) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ uint32_t res = 0;
+ while (c != old_klass.Get()) {
+ DCHECK_NE(c, art::GetClassRoot<art::mirror::Object>());
+ res++;
+ c = c->GetSuperClass();
+ }
+ return res;
+ };
+ auto compare_handles = [&](auto l, auto r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return count_distance(l.Get()) < count_distance(r.Get());
+ };
+ // Sort them by the distance to the base-class. This ensures that any class occurs before any of
+ // its subtypes.
+ std::sort(old_types.begin(), old_types.end(), compare_handles);
+ }
+ for (uint32_t i = 0; i < old_types.size(); ++i) {
+ old_classes_arr->Set(i, old_types[i].Get());
+ }
+ cur_data->SetOldClasses(old_classes_arr.Get());
+ art::Handle<art::mirror::ObjectArray<art::mirror::Class>> new_classes_arr(
+ hs.NewHandle(art::mirror::ObjectArray<art::mirror::Class>::Alloc(
+ driver_->self_, cls_array_class.Get(), old_types.size())));
+ if (new_classes_arr.IsNull()) {
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate new_classes arrays!");
+ return false;
+ }
+ art::MutableHandle<art::mirror::DexCache> dch(hs.NewHandle<art::mirror::DexCache>(nullptr));
+ art::MutableHandle<art::mirror::Class> superclass(hs.NewHandle<art::mirror::Class>(nullptr));
+ for (size_t i = 0; i < old_types.size(); i++) {
+ art::Handle<art::mirror::Class>& old_class = old_types[i];
+ if (old_class.Get() == cur_data->GetMirrorClass()) {
+ CHECK_EQ(i, 0u) << "original class not at index 0. Bad sort!";
+ new_classes_arr->Set(i, cur_data->GetNewClassObject());
+ continue;
+ } else {
+ auto old_super = std::find_if(old_types.begin(),
+ old_types.begin() + i,
+ [&](art::Handle<art::mirror::Class>& v)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return v.Get() == old_class->GetSuperClass();
+ });
+ // Only the GetMirrorClass should not be in this list.
+ CHECK(old_super != old_types.begin() + i)
+ << "from first " << i << " could not find super of " << old_class->PrettyClass()
+ << " expected to find " << old_class->GetSuperClass()->PrettyClass();
+ superclass.Assign(new_classes_arr->Get(std::distance(old_types.begin(), old_super)));
+ dch.Assign(old_class->GetDexCache());
+ art::ObjPtr<art::mirror::Class> new_class(
+ AllocateNewClassObject(old_class, superclass, dch, old_class->GetDexClassDefIndex()));
+ if (new_class == nullptr) {
+ return false;
+ }
+ new_classes_arr->Set(i, new_class);
+ }
+ }
+ cur_data->SetNewClasses(new_classes_arr.Get());
art::Handle<art::mirror::Class> obj_array_class(
hs.NewHandle(art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Object>>(
@@ -1672,9 +1762,18 @@
RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate new_instance arrays!");
return false;
}
- art::Handle<art::mirror::Class> new_klass(hs.NewHandle(cur_data->GetNewClassObject()));
- for (uint32_t i = 0; i < old_instances.size(); ++i) {
- art::ObjPtr<art::mirror::Object> new_instance(new_klass->AllocObject(driver_->self_));
+ for (auto pair : art::ZipCount(art::IterationRange(old_instances.begin(), old_instances.end()))) {
+ art::Handle<art::mirror::Object> hinstance(pair.first);
+ int32_t i = pair.second;
+ auto iterator = art::ZipLeft(old_classes_arr.Iterate<art::mirror::Class>(),
+ new_classes_arr.Iterate<art::mirror::Class>());
+ auto [_, new_type] =
+ *(std::find_if(iterator.begin(),
+ iterator.end(),
+ [&](auto class_pair) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return class_pair.first == hinstance->GetClass();
+ }));
+ art::ObjPtr<art::mirror::Object> new_instance(new_type->AllocObject(driver_->self_));
if (new_instance.IsNull()) {
driver_->self_->AssertPendingOOMException();
driver_->self_->ClearException();
@@ -1734,8 +1833,6 @@
art::Handle<art::mirror::Class> nc(hs.NewHandle(
AllocateNewClassObject(hs.NewHandle(cur_data->GetNewDexCache()))));
if (nc.IsNull()) {
- driver_->self_->ClearException();
- RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate new class object");
return false;
}
@@ -1749,18 +1846,15 @@
return true;
}
-uint32_t Redefiner::ClassRedefinition::GetNewClassSize(bool with_embedded_tables,
- art::Handle<art::mirror::Class> old_klass) {
- // TODO Once we can add methods this won't work any more.
- uint32_t num_vtable_entries = old_klass->GetVTableLength();
+uint32_t Redefiner::ClassRedefinition::GetNewClassSize(art::ClassAccessor& accessor) {
uint32_t num_8bit_static_fields = 0;
uint32_t num_16bit_static_fields = 0;
uint32_t num_32bit_static_fields = 0;
uint32_t num_64bit_static_fields = 0;
uint32_t num_ref_static_fields = 0;
- art::ClassAccessor accessor(*dex_file_, dex_file_->GetClassDef(0));
for (const art::ClassAccessor::Field& f : accessor.GetStaticFields()) {
- std::string_view desc(dex_file_->GetFieldTypeDescriptor(dex_file_->GetFieldId(f.GetIndex())));
+ std::string_view desc(accessor.GetDexFile().GetFieldTypeDescriptor(
+ accessor.GetDexFile().GetFieldId(f.GetIndex())));
if (desc[0] == 'L' || desc[0] == '[') {
num_ref_static_fields++;
} else if (desc == "Z" || desc == "B") {
@@ -1776,8 +1870,8 @@
}
}
- return art::mirror::Class::ComputeClassSize(with_embedded_tables,
- with_embedded_tables ? num_vtable_entries : 0,
+ return art::mirror::Class::ComputeClassSize(/*has_embedded_vtable=*/ false,
+ /*num_vtable_entries=*/ 0,
num_8bit_static_fields,
num_16bit_static_fields,
num_32bit_static_fields,
@@ -1788,23 +1882,41 @@
art::ObjPtr<art::mirror::Class>
Redefiner::ClassRedefinition::AllocateNewClassObject(art::Handle<art::mirror::DexCache> cache) {
+ art::StackHandleScope<2> hs(driver_->self_);
+ art::Handle<art::mirror::Class> old_class(hs.NewHandle(GetMirrorClass()));
+ art::Handle<art::mirror::Class> super_class(hs.NewHandle(old_class->GetSuperClass()));
+ return AllocateNewClassObject(old_class, super_class, cache, /*dex_class_def_index*/0);
+}
+
+art::ObjPtr<art::mirror::Class> Redefiner::ClassRedefinition::AllocateNewClassObject(
+ art::Handle<art::mirror::Class> old_class,
+ art::Handle<art::mirror::Class> super_class,
+ art::Handle<art::mirror::DexCache> cache,
+ uint16_t dex_class_def_index) {
// This is a stripped down DefineClass. We don't want to use DefineClass directly because it needs
// to perform a lot of extra steps to tell the ClassTable and the jit and everything about a new
// class. For now we will need to rely on our tests catching any issues caused by changes in how
// class_linker sets up classes.
// TODO Unify/move this into ClassLinker maybe.
- art::StackHandleScope<5> hs(driver_->self_);
+ art::StackHandleScope<3> hs(driver_->self_);
art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
- art::Handle<art::mirror::Class> old_class(hs.NewHandle(GetMirrorClass()));
+ const art::DexFile* dex_file = cache->GetDexFile();
+ art::ClassAccessor accessor(*dex_file, dex_class_def_index);
art::Handle<art::mirror::Class> new_class(hs.NewHandle(linker->AllocClass(
- driver_->self_, GetNewClassSize(/*with_embedded_tables=*/false, old_class))));
+ driver_->self_, GetNewClassSize(accessor))));
if (new_class.IsNull()) {
driver_->self_->AssertPendingOOMException();
- JVMTI_LOG(ERROR, driver_->env_) << "Unable to allocate new class object!";
+ RecordFailure(
+ ERR(OUT_OF_MEMORY),
+ "Unable to allocate class object for redefinition of " + old_class->PrettyClass());
+ driver_->self_->ClearException();
return nullptr;
}
new_class->SetDexCache(cache.Get());
- linker->SetupClass(*dex_file_, dex_file_->GetClassDef(0), new_class, old_class->GetClassLoader());
+ linker->SetupClass(*dex_file,
+ dex_file->GetClassDef(dex_class_def_index),
+ new_class,
+ old_class->GetClassLoader());
// Make sure we are ready for linking. The lock isn't really needed since this isn't visible to
// other threads but the linker expects it.
@@ -1812,31 +1924,46 @@
new_class->SetClinitThreadId(driver_->self_->GetTid());
// Make sure we have a valid empty iftable even if there are errors.
new_class->SetIfTable(art::GetClassRoot<art::mirror::Object>(linker)->GetIfTable());
- linker->LoadClass(driver_->self_, *dex_file_, dex_file_->GetClassDef(0), new_class);
+ linker->LoadClass(
+ driver_->self_, *dex_file, dex_file->GetClassDef(dex_class_def_index), new_class);
// NB. We know the interfaces and supers didn't change! :)
art::MutableHandle<art::mirror::Class> linked_class(hs.NewHandle<art::mirror::Class>(nullptr));
art::Handle<art::mirror::ObjectArray<art::mirror::Class>> proxy_ifaces(
hs.NewHandle<art::mirror::ObjectArray<art::mirror::Class>>(nullptr));
// No changing hierarchy so everything is loaded.
- new_class->SetSuperClass(old_class->GetSuperClass());
+ new_class->SetSuperClass(super_class.Get());
art::mirror::Class::SetStatus(new_class, art::ClassStatus::kLoaded, nullptr);
if (!linker->LinkClass(driver_->self_, nullptr, new_class, proxy_ifaces, &linked_class)) {
- JVMTI_LOG(ERROR, driver_->env_)
- << "failed to link class due to "
+ std::ostringstream oss;
+ oss << "failed to link class due to "
<< (driver_->self_->IsExceptionPending() ? driver_->self_->GetException()->Dump()
: " unknown");
+ RecordFailure(ERR(INTERNAL), oss.str());
driver_->self_->ClearException();
return nullptr;
}
- // We will initialize it manually.
+ // Everything is already resolved.
art::ObjectLock<art::mirror::Class> objlock(driver_->self_, linked_class);
- // We already verified the class earlier. No need to do it again.
- linked_class->SetVerificationAttempted();
// Mark the class as initialized.
- CHECK(old_class->IsInitialized())
- << "Attempting to redefine an uninitalized class " << old_class->PrettyClass()
+ CHECK(old_class->IsResolved())
+ << "Attempting to redefine an unresolved class " << old_class->PrettyClass()
<< " status=" << old_class->GetStatus();
- linker->ForceClassInitialized(driver_->self_, linked_class);
+ CHECK(linked_class->IsResolved());
+ if (old_class->WasVerificationAttempted()) {
+ // Match verification-attempted flag
+ linked_class->SetVerificationAttempted();
+ }
+ if (old_class->ShouldSkipHiddenApiChecks()) {
+ // Match skip hiddenapi flag
+ linked_class->SetSkipHiddenApiChecks();
+ }
+ if (old_class->IsInitialized()) {
+ // We already verified the class earlier. No need to do it again.
+ linker->ForceClassInitialized(driver_->self_, linked_class);
+ } else if (old_class->GetStatus() > linked_class->GetStatus()) {
+ // We want to match the old status.
+ art::mirror::Class::SetStatus(linked_class, old_class->GetStatus(), driver_->self_);
+ }
// Make sure we have ext-data space for method & field ids. We won't know if we need them until
// it's too late to create them.
// TODO We might want to remove these arrays if they're not needed.
@@ -1845,7 +1972,9 @@
!art::mirror::Class::EnsureMethodIds(linked_class)) {
driver_->self_->AssertPendingOOMException();
driver_->self_->ClearException();
- JVMTI_LOG(ERROR, driver_->env_) << "Unable to allocate jni-id arrays!";
+ RecordFailure(
+ ERR(OUT_OF_MEMORY),
+ "Unable to allocate jni-id arrays for redefinition of " + old_class->PrettyClass());
return nullptr;
}
// Finish setting up methods.
@@ -1862,6 +1991,9 @@
DCHECK_EQ(f->GetDeclaringClass(), linked_class.Get());
});
}
+ // Reset ClinitThreadId back to the thread that loaded the old class. This is needed if we are in
+ // the middle of initializing a class.
+ linked_class->SetClinitThreadId(old_class->GetClinitThreadId());
return linked_class.Get();
}
@@ -1892,9 +2024,39 @@
return false;
}
}
+ if (std::any_of(
+ redefinitions_.begin(),
+ redefinitions_.end(),
+ std::function<bool(ClassRedefinition&)>(&ClassRedefinition::IsStructuralRedefinition))) {
+ return CheckClassHierarchy();
+ }
return true;
}
+bool Redefiner::CheckClassHierarchy() {
+ return std::all_of(
+ redefinitions_.begin(),
+ redefinitions_.end(),
+ [&](ClassRedefinition& r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return std::none_of(
+ redefinitions_.begin(),
+ redefinitions_.end(),
+ [&](ClassRedefinition& r2) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ bool related = &r2 != &r &&
+ r2.GetMirrorClass()->IsAssignableFrom(r.GetMirrorClass()) &&
+ r2.IsStructuralRedefinition();
+ if (related) {
+ std::ostringstream oss;
+ oss << "Structurally redefining " << r2.GetMirrorClass()->PrettyClass()
+ << " which is a superclass of " << r.GetMirrorClass()->PrettyClass()
+ << ". This is not currently supported";
+ r2.RecordFailure(ERR(INTERNAL), oss.str());
+ }
+ return related;
+ });
+ });
+}
+
void Redefiner::RestoreObsoleteMethodMapsIfUnneeded(RedefinitionDataHolder& holder) {
for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
data.GetRedefinition().RestoreObsoleteMethodMapsIfUnneeded(&data);
@@ -1968,6 +2130,142 @@
art::Thread* self_;
};
+class ClassDefinitionPauser : public art::ClassLoadCallback {
+ public:
+ explicit ClassDefinitionPauser(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_)
+ : self_(self),
+ is_running_(false),
+ barrier_(0),
+ release_mu_("SuspendClassDefinition lock", art::kGenericBottomLock),
+ release_barrier_(0),
+ release_cond_("SuspendClassDefinition condvar", release_mu_),
+ count_(0),
+ release_(false) {
+ art::Locks::mutator_lock_->AssertSharedHeld(self_);
+ }
+ ~ClassDefinitionPauser() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::Locks::mutator_lock_->AssertSharedHeld(self_);
+ if (is_running_) {
+ uint32_t count;
+ // Wake up everything.
+ {
+ art::MutexLock mu(self_, release_mu_);
+ release_ = true;
+ count = count_;
+ release_cond_.Broadcast(self_);
+ }
+ // Wait for all threads to leave this structs code.
+ art::ScopedThreadSuspension sts(self_, art::ThreadState::kWaiting);
+ VLOG(plugin) << "Resuming " << count << " threads paused before class-allocation!";
+ release_barrier_.Increment(self_, count);
+ }
+ }
+ void BeginDefineClass() override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::Thread* this_thread = art::Thread::Current();
+ if (this_thread == self_) {
+ // Allow the redefining thread to do whatever.
+ return;
+ }
+ if (this_thread->GetDefineClassCount() != 0) {
+ // We are in the middle of a recursive define-class. Don't suspend now allow it to finish.
+ VLOG(plugin) << "Recursive DefineClass in " << *this_thread
+ << " allowed to proceed despite class-def pause initiated by " << *self_;
+ return;
+ }
+ art::ScopedThreadSuspension sts(this_thread, art::ThreadState::kSuspended);
+ {
+ art::MutexLock mu(this_thread, release_mu_);
+ if (release_) {
+ // Count already retrieved, no need to pass.
+ return;
+ }
+ VLOG(plugin) << "Suspending " << *this_thread << " due to class definition. class-def pause "
+ << "initiated by " << *self_;
+ count_++;
+ while (!release_) {
+ release_cond_.Wait(this_thread);
+ }
+ }
+ release_barrier_.Pass(this_thread);
+ }
+ void EndDefineClass() override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::Thread* this_thread = art::Thread::Current();
+ if (this_thread == self_) {
+ // Allow the redefining thread to do whatever.
+ return;
+ }
+ if (this_thread->GetDefineClassCount() == 0) {
+ // We are done with defining classes.
+ barrier_.Pass(this_thread);
+ }
+ }
+
+ void ClassLoad(art::Handle<art::mirror::Class> klass ATTRIBUTE_UNUSED) override {}
+ void ClassPrepare(art::Handle<art::mirror::Class> klass1 ATTRIBUTE_UNUSED,
+ art::Handle<art::mirror::Class> klass2 ATTRIBUTE_UNUSED) override {}
+
+ void SetRunning() {
+ is_running_ = true;
+ }
+ void WaitFor(uint32_t t) REQUIRES(!art::Locks::mutator_lock_) {
+ barrier_.Increment(self_, t);
+ }
+
+ private:
+ art::Thread* self_;
+ bool is_running_;
+ art::Barrier barrier_;
+ art::Mutex release_mu_;
+ art::Barrier release_barrier_;
+ art::ConditionVariable release_cond_;
+ uint32_t count_ GUARDED_BY(release_mu_);
+ bool release_;
+};
+
+class ScopedSuspendClassLoading {
+ public:
+ ScopedSuspendClassLoading(art::Thread* self, art::Runtime* runtime, RedefinitionDataHolder& h)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ : self_(self), runtime_(runtime), paused_(false), pauser_(self_) {
+ if (std::any_of(h.begin(), h.end(), [](auto r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return r.GetRedefinition().IsStructuralRedefinition();
+ })) {
+ VLOG(plugin) << "Pausing Class loading for structural redefinition.";
+ paused_ = true;
+ {
+ art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative);
+ uint32_t in_progress_defines = 0;
+ {
+ art::ScopedSuspendAll ssa(__FUNCTION__);
+ pauser_.SetRunning();
+ runtime_->GetRuntimeCallbacks()->AddClassLoadCallback(&pauser_);
+ art::MutexLock mu(self_, *art::Locks::thread_list_lock_);
+ runtime_->GetThreadList()->ForEach([&](art::Thread* t) {
+ if (t != self_ && t->GetDefineClassCount() != 0) {
+ in_progress_defines++;
+ }
+ });
+ VLOG(plugin) << "Waiting for " << in_progress_defines << " in progress class-loads to finish";
+ }
+ pauser_.WaitFor(in_progress_defines);
+ }
+ }
+ }
+ ~ScopedSuspendClassLoading() {
+ if (paused_) {
+ art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative);
+ art::ScopedSuspendAll ssa(__FUNCTION__);
+ runtime_->GetRuntimeCallbacks()->RemoveClassLoadCallback(&pauser_);
+ }
+ }
+
+ private:
+ art::Thread* self_;
+ art::Runtime* runtime_;
+ bool paused_;
+ ClassDefinitionPauser pauser_;
+};
+
class ScopedSuspendAllocations {
public:
ScopedSuspendAllocations(art::Runtime* runtime, RedefinitionDataHolder& h)
@@ -2022,6 +2320,7 @@
return result_;
}
+ ScopedSuspendClassLoading suspend_class_load(self_, runtime_, holder);
ScopedSuspendAllocations suspend_alloc(runtime_, holder);
if (!CollectAndCreateNewInstances(holder)) {
return result_;
@@ -2153,27 +2452,28 @@
const RedefinitionDataIter& data,
std::map<art::ArtMethod*, art::ArtMethod*>* method_map,
std::map<art::ArtField*, art::ArtField*>* field_map) {
- art::ObjPtr<art::mirror::Class> old_cls(data.GetMirrorClass());
- art::ObjPtr<art::mirror::Class> new_cls(data.GetNewClassObject());
- for (art::ArtField& f : old_cls->GetSFields()) {
- (*field_map)[&f] = new_cls->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
- }
- for (art::ArtField& f : old_cls->GetIFields()) {
- (*field_map)[&f] = new_cls->FindDeclaredInstanceField(f.GetName(), f.GetTypeDescriptor());
- }
- auto new_methods = new_cls->GetMethods(art::kRuntimePointerSize);
- for (art::ArtMethod& m : old_cls->GetMethods(art::kRuntimePointerSize)) {
- // No support for finding methods in this way since it's generally not needed. Just do it the
- // easy way.
- auto nm_iter = std::find_if(
- new_methods.begin(),
- new_methods.end(),
- [&](art::ArtMethod& cand) REQUIRES_SHARED(art::Locks::mutator_lock_) {
- return cand.GetNameView() == m.GetNameView() && cand.GetSignature() == m.GetSignature();
- });
- CHECK(nm_iter != new_methods.end())
- << "Could not find redefined version of " << m.PrettyMethod();
- (*method_map)[&m] = &(*nm_iter);
+ for (auto [new_cls, old_cls] :
+ art::ZipLeft(data.GetNewClasses()->Iterate(), data.GetOldClasses()->Iterate())) {
+ for (art::ArtField& f : old_cls->GetSFields()) {
+ (*field_map)[&f] = new_cls->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
+ }
+ for (art::ArtField& f : old_cls->GetIFields()) {
+ (*field_map)[&f] = new_cls->FindDeclaredInstanceField(f.GetName(), f.GetTypeDescriptor());
+ }
+ auto new_methods = new_cls->GetMethods(art::kRuntimePointerSize);
+ for (art::ArtMethod& m : old_cls->GetMethods(art::kRuntimePointerSize)) {
+ // No support for finding methods in this way since it's generally not needed. Just do it the
+ // easy way.
+ auto nm_iter = std::find_if(
+ new_methods.begin(),
+ new_methods.end(),
+ [&](art::ArtMethod& cand) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return cand.GetNameView() == m.GetNameView() && cand.GetSignature() == m.GetSignature();
+ });
+ CHECK(nm_iter != new_methods.end())
+ << "Could not find redefined version of " << m.PrettyMethod();
+ (*method_map)[&m] = &(*nm_iter);
+ }
}
}
@@ -2300,6 +2600,8 @@
art::ScopedAssertNoThreadSuspension sants(__FUNCTION__);
art::ObjPtr<art::mirror::Class> orig(holder.GetMirrorClass());
art::ObjPtr<art::mirror::Class> replacement(holder.GetNewClassObject());
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> new_classes(holder.GetNewClasses());
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> old_classes(holder.GetOldClasses());
// Collect mappings from old to new fields/methods
std::map<art::ArtMethod*, art::ArtMethod*> method_map;
std::map<art::ArtField*, art::ArtField*> field_map;
@@ -2310,13 +2612,26 @@
holder.GetOldInstanceObjects());
CHECK(!orig.IsNull());
CHECK(!replacement.IsNull());
+ // Once we do the ReplaceReferences old_classes will have the new_classes in it. We want to keep
+ // ahold of the old classes so copy them now.
+ std::vector<art::ObjPtr<art::mirror::Class>> old_classes_vec(old_classes->Iterate().begin(),
+ old_classes->Iterate().end());
// Copy over the static fields of the class and all the instance fields.
- CopyAndClearFields(/*is_static=*/true, replacement, replacement, orig, orig);
+ for (auto [new_class, old_class] : art::ZipLeft(new_classes->Iterate(), old_classes->Iterate())) {
+ CHECK(!new_class.IsNull());
+ CHECK(!old_class.IsNull());
+ CHECK(!old_class->IsErroneous());
+ if (old_class->GetStatus() > new_class->GetStatus()) {
+ // Some verification/initialization step happened during interval between
+ // creating the new class and now. Just copy the new status.
+ new_class->SetStatusLocked(old_class->GetStatus());
+ }
+ CopyAndClearFields(true, new_class, new_class, old_class, old_class);
+ }
// Copy and clear the fields of the old-instances.
- for (int32_t i = 0; i < old_instances->GetLength(); i++) {
- art::ObjPtr<art::mirror::Object> old_instance(old_instances->Get(i));
- art::ObjPtr<art::mirror::Object> new_instance(new_instances->Get(i));
+ for (auto [new_instance, old_instance] :
+ art::ZipLeft(new_instances->Iterate(), old_instances->Iterate())) {
CopyAndClearFields(/*is_static=*/false,
new_instance,
new_instance->GetClass(),
@@ -2324,12 +2639,15 @@
old_instance->GetClass());
}
// Mark old class obsolete.
- orig->SetObsoleteObject();
- // Mark methods obsolete. We need to wait until later to actually clear the jit data.
- for (art::ArtMethod& m : orig->GetMethods(art::kRuntimePointerSize)) {
- m.SetIsObsolete();
- m.SetDontCompile();
- DCHECK_EQ(orig, m.GetDeclaringClass());
+ for (auto old_class : old_classes->Iterate()) {
+ old_class->SetObsoleteObject();
+ // Mark methods obsolete. We need to wait until later to actually clear the jit data.
+ for (art::ArtMethod& m : old_class->GetMethods(art::kRuntimePointerSize)) {
+ m.SetIsObsolete();
+ if (m.IsInvokable()) {
+ m.SetDontCompile();
+ }
+ }
}
// Update live pointers in ART code.
auto could_change_resolution_of = [&](auto* field_or_method,
@@ -2418,9 +2736,16 @@
std::unordered_map<art::ObjPtr<art::mirror::Object>,
art::ObjPtr<art::mirror::Object>,
art::HashObjPtr> map;
- map.emplace(orig, replacement);
- for (int32_t i = 0; i < old_instances->GetLength(); i++) {
- map.emplace(old_instances->Get(i), new_instances->Get(i));
+ for (auto [new_class, old_class] : art::ZipLeft(new_classes->Iterate(), old_classes->Iterate())) {
+ map.emplace(old_class, new_class);
+ }
+ for (auto [new_instance, old_instance] :
+ art::ZipLeft(new_instances->Iterate(), old_instances->Iterate())) {
+ map.emplace(old_instance, new_instance);
+ // Bare-bones check that the mapping is correct.
+ CHECK(new_instance->GetClass() == map[old_instance->GetClass()]->AsClass())
+ << new_instance->GetClass()->PrettyClass() << " vs "
+ << map[old_instance->GetClass()]->AsClass()->PrettyClass();
}
// Actually perform the general replacement. This doesn't affect ArtMethod/ArtFields. It does
@@ -2430,8 +2755,10 @@
// Save the old class so that the JIT gc doesn't get confused by it being collected before the
// jit code. This is also needed to keep the dex-caches of any obsolete methods live.
- replacement->GetExtData()->SetObsoleteClass(orig);
-
+ for (auto [new_class, old_class] :
+ art::ZipLeft(new_classes->Iterate(), art::MakeIterationRange(old_classes_vec))) {
+ new_class->GetExtData()->SetObsoleteClass(old_class);
+ }
art::jit::Jit* jit = driver_->runtime_->GetJit();
if (jit != nullptr) {
@@ -2454,6 +2781,10 @@
// Just make sure we didn't screw up any of the now obsolete methods or fields. We need their
// declaring-class to still be the obolete class
orig->VisitMethods([&](art::ArtMethod* method) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (method->IsCopied()) {
+ // Copied methods have interfaces as their declaring class.
+ return;
+ }
DCHECK_EQ(method->GetDeclaringClass(), orig) << method->GetDeclaringClass()->PrettyClass()
<< " vs " << orig->PrettyClass();
}, art::kRuntimePointerSize);
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index cedce92..36bc9d3 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -52,6 +52,7 @@
#include "obj_ptr.h"
namespace art {
+class ClassAccessor;
namespace dex {
struct ClassDef;
} // namespace dex
@@ -168,10 +169,15 @@
void FindAndAllocateObsoleteMethods(art::ObjPtr<art::mirror::Class> art_klass)
REQUIRES(art::Locks::mutator_lock_);
+ art::ObjPtr<art::mirror::Class> AllocateNewClassObject(
+ art::Handle<art::mirror::Class> old_class,
+ art::Handle<art::mirror::Class> super_class,
+ art::Handle<art::mirror::DexCache> cache,
+ uint16_t dex_class_def_index) REQUIRES_SHARED(art::Locks::mutator_lock_);
art::ObjPtr<art::mirror::Class> AllocateNewClassObject(art::Handle<art::mirror::DexCache> cache)
REQUIRES_SHARED(art::Locks::mutator_lock_);
- uint32_t GetNewClassSize(bool with_embedded_tables, art::Handle<art::mirror::Class> old_class)
+ uint32_t GetNewClassSize(art::ClassAccessor& accessor)
REQUIRES_SHARED(art::Locks::mutator_lock_);
// Checks that the dex file contains only the single expected class and that the top-level class
@@ -317,6 +323,7 @@
jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_);
bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool CheckClassHierarchy() REQUIRES_SHARED(art::Locks::mutator_lock_);
bool CheckAllClassesAreVerified(RedefinitionDataHolder& holder)
REQUIRES_SHARED(art::Locks::mutator_lock_);
bool EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder)
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 3f904f3..22841bd 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3366,12 +3366,57 @@
StartsWith(descriptor_sv, "Landroid/media/");
}
+// Helper for maintaining DefineClass counting. We need to notify callbacks when we start/end a
+// define-class and how many recursive DefineClasses we are at in order to allow for doing things
+// like pausing class definition.
+struct ScopedDefiningClass {
+ public:
+ explicit ScopedDefiningClass(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_)
+ : self_(self), returned_(false) {
+ Locks::mutator_lock_->AssertSharedHeld(self_);
+ Runtime::Current()->GetRuntimeCallbacks()->BeginDefineClass();
+ self_->IncrDefineClassCount();
+ }
+ ~ScopedDefiningClass() REQUIRES_SHARED(Locks::mutator_lock_) {
+ Locks::mutator_lock_->AssertSharedHeld(self_);
+ CHECK(returned_);
+ }
+
+ ObjPtr<mirror::Class> Finish(Handle<mirror::Class> h_klass)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ CHECK(!returned_);
+ self_->DecrDefineClassCount();
+ Runtime::Current()->GetRuntimeCallbacks()->EndDefineClass();
+ Thread::PoisonObjectPointersIfDebug();
+ returned_ = true;
+ return h_klass.Get();
+ }
+
+ ObjPtr<mirror::Class> Finish(ObjPtr<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ StackHandleScope<1> hs(self_);
+ Handle<mirror::Class> h_klass(hs.NewHandle(klass));
+ return Finish(h_klass);
+ }
+
+ ObjPtr<mirror::Class> Finish(nullptr_t np ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ScopedNullHandle<mirror::Class> snh;
+ return Finish(snh);
+ }
+
+ private:
+ Thread* self_;
+ bool returned_;
+};
+
ObjPtr<mirror::Class> ClassLinker::DefineClass(Thread* self,
const char* descriptor,
size_t hash,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
const dex::ClassDef& dex_class_def) {
+ ScopedDefiningClass sdc(self);
StackHandleScope<3> hs(self);
auto klass = hs.NewHandle<mirror::Class>(nullptr);
@@ -3402,7 +3447,7 @@
ObjPtr<mirror::Throwable> pre_allocated =
Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
self->SetException(pre_allocated);
- return nullptr;
+ return sdc.Finish(nullptr);
}
// This is to prevent the calls to ClassLoad and ClassPrepare which can cause java/user-supplied
@@ -3413,7 +3458,7 @@
ObjPtr<mirror::Throwable> pre_allocated =
Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
self->SetException(pre_allocated);
- return nullptr;
+ return sdc.Finish(nullptr);
}
if (klass == nullptr) {
@@ -3424,12 +3469,12 @@
if (CanAllocClass()) {
klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
} else {
- return nullptr;
+ return sdc.Finish(nullptr);
}
}
if (UNLIKELY(klass == nullptr)) {
self->AssertPendingOOMException();
- return nullptr;
+ return sdc.Finish(nullptr);
}
// Get the real dex file. This will return the input if there aren't any callbacks or they do
// nothing.
@@ -3446,12 +3491,12 @@
&new_class_def);
// Check to see if an exception happened during runtime callbacks. Return if so.
if (self->IsExceptionPending()) {
- return nullptr;
+ return sdc.Finish(nullptr);
}
ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get());
if (dex_cache == nullptr) {
self->AssertPendingException();
- return nullptr;
+ return sdc.Finish(nullptr);
}
klass->SetDexCache(dex_cache);
SetupClass(*new_dex_file, *new_class_def, klass, class_loader.Get());
@@ -3473,7 +3518,7 @@
if (existing != nullptr) {
// We failed to insert because we raced with another thread. Calling EnsureResolved may cause
// this thread to block.
- return EnsureResolved(self, descriptor, existing);
+ return sdc.Finish(EnsureResolved(self, descriptor, existing));
}
// Load the fields and other things after we are inserted in the table. This is so that we don't
@@ -3488,7 +3533,7 @@
if (!klass->IsErroneous()) {
mirror::Class::SetStatus(klass, ClassStatus::kErrorUnresolved, self);
}
- return nullptr;
+ return sdc.Finish(nullptr);
}
// Finish loading (if necessary) by finding parents
@@ -3498,7 +3543,7 @@
if (!klass->IsErroneous()) {
mirror::Class::SetStatus(klass, ClassStatus::kErrorUnresolved, self);
}
- return nullptr;
+ return sdc.Finish(nullptr);
}
CHECK(klass->IsLoaded());
@@ -3517,7 +3562,7 @@
if (!klass->IsErroneous()) {
mirror::Class::SetStatus(klass, ClassStatus::kErrorUnresolved, self);
}
- return nullptr;
+ return sdc.Finish(nullptr);
}
self->AssertNoPendingException();
CHECK(h_new_class != nullptr) << descriptor;
@@ -3551,7 +3596,7 @@
// Notify native debugger of the new class and its layout.
jit::Jit::NewTypeLoadedIfUsingJit(h_new_class.Get());
- return h_new_class.Get();
+ return sdc.Finish(h_new_class);
}
uint32_t ClassLinker::SizeOfClassWithoutEmbeddedTables(const DexFile& dex_file,
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 877b964..2687b9a 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -1441,6 +1441,10 @@
public:
virtual ~ClassLoadCallback() {}
+ // Called immediately before beginning class-definition and immediately before returning from it.
+ virtual void BeginDefineClass() REQUIRES_SHARED(Locks::mutator_lock_) {}
+ virtual void EndDefineClass() REQUIRES_SHARED(Locks::mutator_lock_) {}
+
// If set we will replace initial_class_def & initial_dex_file with the final versions. The
// callback author is responsible for ensuring these are allocated in such a way they can be
// cleaned up if another transformation occurs. Note that both must be set or null/unchanged on
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index cc1a7f8..c67b1b0 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -358,7 +358,8 @@
DCHECK(self->IsExceptionPending()); // Throw exception and unwind.
return nullptr; // Failure.
}
- if (UNLIKELY(is_set && resolved_field->IsFinal() && (fields_class != referring_class))) {
+ if (UNLIKELY(is_set && resolved_field->IsFinal() && (fields_class != referring_class) &&
+ !referring_class->IsObsoleteVersionOf(fields_class))) {
ThrowIllegalAccessErrorFinalField(referrer, resolved_field);
return nullptr; // Failure.
} else {
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index db5cbce..79d767d 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -550,6 +550,22 @@
access_to, method, dex_cache, method_idx, throw_invoke_type);
}
+inline bool Class::IsObsoleteVersionOf(ObjPtr<Class> klass) {
+ DCHECK(!klass->IsObsoleteObject()) << klass->PrettyClass() << " is obsolete!";
+ if (LIKELY(!IsObsoleteObject())) {
+ return false;
+ }
+ ObjPtr<Class> current(klass);
+ do {
+ if (UNLIKELY(current == this)) {
+ return true;
+ } else {
+ current = current->GetObsoleteClass();
+ }
+ } while (!current.IsNull());
+ return false;
+}
+
inline bool Class::IsSubClass(ObjPtr<Class> klass) {
// Since the SubtypeCheck::IsSubtypeOf needs to lookup the Depth,
// it is always O(Depth) in terms of speed to do the check.
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index b7429dd..e57cc98 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -188,28 +188,57 @@
}
}
+template <typename T>
+static void CheckSetStatus(Thread* self, T thiz, ClassStatus new_status, ClassStatus old_status)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (UNLIKELY(new_status <= old_status && new_status != ClassStatus::kErrorUnresolved &&
+ new_status != ClassStatus::kErrorResolved && new_status != ClassStatus::kRetired)) {
+ LOG(FATAL) << "Unexpected change back of class status for " << thiz->PrettyClass() << " "
+ << old_status << " -> " << new_status;
+ }
+ if (old_status == ClassStatus::kInitialized) {
+ // We do not hold the lock for making the class visibly initialized
+ // as this is unnecessary and could lead to deadlocks.
+ CHECK_EQ(new_status, ClassStatus::kVisiblyInitialized);
+ } else if ((new_status >= ClassStatus::kResolved || old_status >= ClassStatus::kResolved) &&
+ !Locks::mutator_lock_->IsExclusiveHeld(self)) {
+ // When classes are being resolved the resolution code should hold the
+ // lock or have everything else suspended
+ CHECK_EQ(thiz->GetLockOwnerThreadId(), self->GetThreadId())
+ << "Attempt to change status of class while not holding its lock: " << thiz->PrettyClass()
+ << " " << old_status << " -> " << new_status;
+ }
+ if (UNLIKELY(Locks::mutator_lock_->IsExclusiveHeld(self))) {
+ CHECK(!Class::IsErroneous(new_status))
+ << "status " << new_status
+ << " cannot be set while suspend-all is active. Would require allocations.";
+ CHECK(thiz->IsResolved())
+ << thiz->PrettyClass()
+ << " not resolved during suspend-all status change. Waiters might be missed!";
+ }
+}
+
+void Class::SetStatusLocked(ClassStatus new_status) {
+ ClassStatus old_status = GetStatus();
+ CheckSetStatus(Thread::Current(), this, new_status, old_status);
+ if (kBitstringSubtypeCheckEnabled) {
+ // FIXME: This looks broken with respect to aborted transactions.
+ SubtypeCheck<ObjPtr<mirror::Class>>::WriteStatus(this, new_status);
+ } else {
+ // The ClassStatus is always in the 4 most-significant bits of status_.
+ static_assert(sizeof(status_) == sizeof(uint32_t), "Size of status_ not equal to uint32");
+ uint32_t new_status_value = static_cast<uint32_t>(new_status) << (32 - kClassStatusBitSize);
+ DCHECK(!Runtime::Current()->IsActiveTransaction());
+ SetField32Volatile<false>(StatusOffset(), new_status_value);
+ }
+}
+
void Class::SetStatus(Handle<Class> h_this, ClassStatus new_status, Thread* self) {
ClassStatus old_status = h_this->GetStatus();
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
bool class_linker_initialized = class_linker != nullptr && class_linker->IsInitialized();
if (LIKELY(class_linker_initialized)) {
- if (UNLIKELY(new_status <= old_status &&
- new_status != ClassStatus::kErrorUnresolved &&
- new_status != ClassStatus::kErrorResolved &&
- new_status != ClassStatus::kRetired)) {
- LOG(FATAL) << "Unexpected change back of class status for " << h_this->PrettyClass()
- << " " << old_status << " -> " << new_status;
- }
- if (old_status == ClassStatus::kInitialized) {
- // We do not hold the lock for making the class visibly initialized
- // as this is unnecessary and could lead to deadlocks.
- CHECK_EQ(new_status, ClassStatus::kVisiblyInitialized);
- } else if (new_status >= ClassStatus::kResolved || old_status >= ClassStatus::kResolved) {
- // When classes are being resolved the resolution code should hold the lock.
- CHECK_EQ(h_this->GetLockOwnerThreadId(), self->GetThreadId())
- << "Attempt to change status of class while not holding its lock: "
- << h_this->PrettyClass() << " " << old_status << " -> " << new_status;
- }
+ CheckSetStatus(self, h_this, new_status, old_status);
}
if (UNLIKELY(IsErroneous(new_status))) {
CHECK(!h_this->IsErroneous())
@@ -336,6 +365,15 @@
SetField32Transaction(OFFSET_OF_OBJECT_MEMBER(Class, class_size_), new_class_size);
}
+ObjPtr<Class> Class::GetObsoleteClass() {
+ ObjPtr<ClassExt> ext(GetExtData());
+ if (ext.IsNull()) {
+ return nullptr;
+ } else {
+ return ext->GetObsoleteClass();
+ }
+}
+
// Return the class' name. The exact format is bizarre, but it's the specified behavior for
// Class.getName: keywords for primitive types, regular "[I" form for primitive arrays (so "int"
// but "[I"), and arrays of reference types written between "L" and ";" but with dots rather than
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index a8b8235..f5c2614 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -105,6 +105,10 @@
static void SetStatus(Handle<Class> h_this, ClassStatus new_status, Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
+ // Used for structural redefinition to directly set the class-status while
+ // holding a strong mutator-lock.
+ void SetStatusLocked(ClassStatus new_status) REQUIRES(Locks::mutator_lock_);
+
void SetStatusForPrimitiveOrArray(ClassStatus new_status) REQUIRES_SHARED(Locks::mutator_lock_);
static constexpr MemberOffset StatusOffset() {
@@ -617,6 +621,11 @@
// to themselves. Classes for primitive types may not assign to each other.
ALWAYS_INLINE bool IsAssignableFrom(ObjPtr<Class> src) REQUIRES_SHARED(Locks::mutator_lock_);
+ // Checks if 'klass' is a redefined version of this.
+ bool IsObsoleteVersionOf(ObjPtr<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ ObjPtr<Class> GetObsoleteClass() REQUIRES_SHARED(Locks::mutator_lock_);
+
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
ALWAYS_INLINE ObjPtr<Class> GetSuperClass() REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index ac73364..e0f57c0 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -228,6 +228,19 @@
}
}
+void RuntimeCallbacks::EndDefineClass() {
+ for (ClassLoadCallback* cb : COPY(class_callbacks_)) {
+ cb->EndDefineClass();
+ }
+}
+
+void RuntimeCallbacks::BeginDefineClass() {
+ for (ClassLoadCallback* cb : COPY(class_callbacks_)) {
+ cb->BeginDefineClass();
+ }
+}
+
+
void RuntimeCallbacks::ClassPreDefine(const char* descriptor,
Handle<mirror::Class> temp_class,
Handle<mirror::ClassLoader> loader,
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index 7111ba0..3cadd97 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -181,6 +181,8 @@
void AddClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
void RemoveClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
+ void BeginDefineClass() REQUIRES_SHARED(Locks::mutator_lock_);
+ void EndDefineClass() REQUIRES_SHARED(Locks::mutator_lock_);
void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
void ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/thread.h b/runtime/thread.h
index 29375e5..09e8810 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -263,6 +263,17 @@
(state_and_flags.as_struct.flags & kSuspendRequest) != 0;
}
+ void DecrDefineClassCount() {
+ tls32_.define_class_counter--;
+ }
+
+ void IncrDefineClassCount() {
+ tls32_.define_class_counter++;
+ }
+ uint32_t GetDefineClassCount() const {
+ return tls32_.define_class_counter;
+ }
+
// If delta > 0 and (this != self or suspend_barrier is not null), this function may temporarily
// release thread_suspend_count_lock_ internally.
ALWAYS_INLINE
@@ -1550,7 +1561,8 @@
user_code_suspend_count(0),
force_interpreter_count(0),
use_mterp(0),
- make_visibly_initialized_counter(0) {}
+ make_visibly_initialized_counter(0),
+ define_class_counter(0) {}
union StateAndFlags state_and_flags;
static_assert(sizeof(union StateAndFlags) == sizeof(int32_t),
@@ -1648,6 +1660,10 @@
// initialized but not visibly initialized for a long time even if no more classes are
// being initialized anymore.
uint32_t make_visibly_initialized_counter;
+
+ // Counter for how many nested define-classes are ongoing in this thread. Used to allow waiting
+ // for threads to be done with class-definition work.
+ uint32_t define_class_counter;
} tls32_;
struct PACKED(8) tls_64bit_sized_values {
diff --git a/test/1983-structural-redefinition-failures/expected.txt b/test/1983-structural-redefinition-failures/expected.txt
index 40a0914..222aac4 100644
--- a/test/1983-structural-redefinition-failures/expected.txt
+++ b/test/1983-structural-redefinition-failures/expected.txt
@@ -24,13 +24,13 @@
Is Structurally modifiable class java.lang.invoke.VarHandle false
Is Structurally modifiable class java.lang.invoke.FieldVarHandle false
Checking non-mirror'd classes
-Is Structurally modifiable class java.util.ArrayList false
+Is Structurally modifiable class java.util.ArrayList true
Is Structurally modifiable class java.util.Objects true
Is Structurally modifiable class java.util.Arrays true
Is Structurally modifiable class [Ljava.lang.Object; false
Is Structurally modifiable class java.lang.Integer true
-Is Structurally modifiable class java.lang.Number false
+Is Structurally modifiable class java.lang.Number true
Is Structurally modifiable class art.Test1983$NoVirtuals true
-Is Structurally modifiable class art.Test1983$WithVirtuals false
-Is Structurally modifiable class art.Test1983$SubWithVirtuals false
+Is Structurally modifiable class art.Test1983$WithVirtuals true
+Is Structurally modifiable class art.Test1983$SubWithVirtuals true
Is Structurally modifiable class java.lang.invoke.MethodHandles true
diff --git a/test/1993-fallback-non-structural/expected.txt b/test/1993-fallback-non-structural/expected.txt
index 7e2cdf4..f523e70 100644
--- a/test/1993-fallback-non-structural/expected.txt
+++ b/test/1993-fallback-non-structural/expected.txt
@@ -1,3 +1,3 @@
-Can structurally Redefine: false
+Can structurally Redefine: true
hello
Goodbye
diff --git a/test/1993-fallback-non-structural/src/art/Test1993.java b/test/1993-fallback-non-structural/src/art/Test1993.java
index f442099..e2a8f6e 100644
--- a/test/1993-fallback-non-structural/src/art/Test1993.java
+++ b/test/1993-fallback-non-structural/src/art/Test1993.java
@@ -17,6 +17,7 @@
package art;
import java.util.Base64;
+import java.lang.reflect.*;
public class Test1993 {
static class Transform {
@@ -61,17 +62,25 @@
"AAAASAEAAAMgAAACAAAAgAEAAAEQAAABAAAAjAEAAAIgAAAVAAAAkgEAAAQgAAACAAAALAMAAAAg" +
"AAABAAAAOwMAAAMQAAACAAAATAMAAAYgAAABAAAAXAMAAAAQAAABAAAAbAMAAA==");
- public static void run() {
+ public static void run() throws Exception {
Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
doTest(new Transform());
}
- public static void doTest(Transform t) {
- // TODO Remove this once the class is structurally modifiable.
+ public static void doTest(Transform t) throws Exception {
System.out.println("Can structurally Redefine: " +
Redefinition.isStructurallyModifiable(Transform.class));
t.sayHi();
Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
t.sayHi();
+ // Check and make sure we didn't structurally redefine by looking for ClassExt.obsoleteClass
+ Field ext_data_field = Class.class.getDeclaredField("extData");
+ ext_data_field.setAccessible(true);
+ Object ext_data = ext_data_field.get(Transform.class);
+ Field obsolete_class_field = ext_data.getClass().getDeclaredField("obsoleteClass");
+ obsolete_class_field.setAccessible(true);
+ if (obsolete_class_field.get(ext_data) != null) {
+ System.out.println("Expected no ClassExt.obsoleteClass but got " + obsolete_class_field.get(ext_data));
+ }
}
}
diff --git a/test/1999-virtual-structural/expected.txt b/test/1999-virtual-structural/expected.txt
new file mode 100644
index 0000000..335b5d7
--- /dev/null
+++ b/test/1999-virtual-structural/expected.txt
@@ -0,0 +1,4 @@
+Hi(SubTransform called 1 times)
+Hi(SubTransform called 2 times)
+Hi(SubTransform called 3 times)
+Hello (Transform called 1 times)(SubTransform called 4 times)
diff --git a/test/1999-virtual-structural/info.txt b/test/1999-virtual-structural/info.txt
new file mode 100644
index 0000000..606c984
--- /dev/null
+++ b/test/1999-virtual-structural/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition can add new virtual methods and fields.
diff --git a/test/1999-virtual-structural/run b/test/1999-virtual-structural/run
new file mode 100755
index 0000000..0d41632
--- /dev/null
+++ b/test/1999-virtual-structural/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# invoke-virtual-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of vtable methods. Since this test
+# changes the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/1999-virtual-structural/src/Main.java b/test/1999-virtual-structural/src/Main.java
new file mode 100644
index 0000000..86a492b
--- /dev/null
+++ b/test/1999-virtual-structural/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.Test1999.run();
+ }
+}
diff --git a/test/1999-virtual-structural/src/art/Redefinition.java b/test/1999-virtual-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1999-virtual-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1999-virtual-structural/src/art/Test1999.java b/test/1999-virtual-structural/src/art/Test1999.java
new file mode 100644
index 0000000..f6811a9
--- /dev/null
+++ b/test/1999-virtual-structural/src/art/Test1999.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.util.Base64;
+public class Test1999 {
+
+ public static class Transform {
+ public String getGreeting() {
+ return "Hi";
+ }
+ }
+
+ public static class SubTransform extends Transform {
+ private int count = 0;
+ public void sayHi() {
+ System.out.println(getGreeting() + "(SubTransform called " + (++count) + " times)");
+ }
+ }
+
+ /**
+ * base64 encoded class/dex file for
+ * public static class Transform {
+ * private int count;
+ * public String getGreeting() {
+ * return "Hello (Transform called " + incrCount() + " times)";
+ * }
+ * protected int incrCount() {
+ * return ++count;
+ * }
+ * }
+ */
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQAwwbMpPdPdWkU+6UJnvqa7v4VBdcuq2vkMBQAAcAAAAHhWNBIAAAAAAAAAAEgEAAAa" +
+"AAAAcAAAAAkAAADYAAAABQAAAPwAAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAABsAwAAoAEAADoC" +
+"AABDAgAASwIAAGUCAABoAgAAawIAAG8CAABzAgAAjQIAAJ0CAADBAgAA4QIAAPUCAAAJAwAAJAMA" +
+"ADMDAAA+AwAAQQMAAE4DAABWAwAAXQMAAGoDAAB1AwAAewMAAIUDAACMAwAAAwAAAAcAAAAIAAAA" +
+"CQAAAAoAAAALAAAADAAAAA0AAAAQAAAAAwAAAAAAAAAAAAAABAAAAAYAAAAAAAAABQAAAAcAAAAs" +
+"AgAABgAAAAcAAAA0AgAAEAAAAAgAAAAAAAAAAQAAABMAAAABAAQAAQAAAAEAAQAUAAAAAQAAABUA" +
+"AAAFAAQAAQAAAAcABAABAAAABwACABIAAAAHAAMAEgAAAAcAAQAXAAAAAQAAAAEAAAAFAAAAAAAA" +
+"AA4AAAA4BAAAEwQAAAAAAAACAAEAAAAAACgCAAAHAAAAUhAAANgAAAFZEAAADwAAAAQAAQACAAAA" +
+"JAIAABsAAABuEAIAAwAKACIBBwBwEAQAAQAaAgIAbiAGACEAbiAFAAEAGgAAAG4gBgABAG4QBwAB" +
+"AAwAEQAAAAEAAQABAAAAIAIAAAQAAABwEAMAAAAOAAMADgAGAA4ACQAOAAEAAAAAAAAAAQAAAAYA" +
+"ByB0aW1lcykABjxpbml0PgAYSGVsbG8gKFRyYW5zZm9ybSBjYWxsZWQgAAFJAAFMAAJMSQACTEwA" +
+"GExhcnQvVGVzdDE5OTkkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5OTsAIkxkYWx2aWsvYW5ub3Rh" +
+"dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
+"dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
+"bGRlcjsADVRlc3QxOTk5LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MABmFwcGVuZAAF" +
+"Y291bnQAC2dldEdyZWV0aW5nAAlpbmNyQ291bnQABG5hbWUACHRvU3RyaW5nAAV2YWx1ZQB2fn5E" +
+"OHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2" +
+"N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYi" +
+"fQACAwEYGAICBAIRBAkWFw8AAQECAAIAgYAEiAQBAcADAQSgAwAAAAAAAgAAAAQEAAAKBAAALAQA" +
+"AAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAGgAAAHAAAAACAAAACQAAANgAAAADAAAA" +
+"BQAAAPwAAAAEAAAAAQAAADgBAAAFAAAACAAAAEABAAAGAAAAAQAAAIABAAABIAAAAwAAAKABAAAD" +
+"IAAAAwAAACACAAABEAAAAgAAACwCAAACIAAAGgAAADoCAAAEIAAAAgAAAAQEAAAAIAAAAQAAABME" +
+"AAADEAAAAgAAACgEAAAGIAAAAQAAADgEAAAAEAAAAQAAAEgEAAA=");
+
+
+ public static void run() {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest(new SubTransform());
+ }
+
+ public static void doTest(SubTransform t) {
+ t.sayHi();
+ t.sayHi();
+ t.sayHi();
+ Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+ t.sayHi();
+ }
+}
diff --git a/test/2000-virtual-list-structural/AbstractCollection.patch b/test/2000-virtual-list-structural/AbstractCollection.patch
new file mode 100644
index 0000000..7507c7d
--- /dev/null
+++ b/test/2000-virtual-list-structural/AbstractCollection.patch
@@ -0,0 +1,16 @@
+--- ../../../libcore/ojluni/src/main/java/java/util/AbstractCollection.java 2019-05-31 10:36:26.634361294 -0700
++++ src-ex/java/util/AbstractCollection.java 2019-11-18 13:04:48.253575013 -0800
+@@ -63,7 +63,13 @@
+ * Sole constructor. (For invocation by subclass constructors, typically
+ * implicit.)
+ */
++ public static volatile int TOTAL_COUNT;
++ public int cnt;
++
+ protected AbstractCollection() {
++ synchronized (Collection.class) {
++ cnt = ++TOTAL_COUNT;
++ }
+ }
+
+ // Query Operations
diff --git a/test/2000-virtual-list-structural/build b/test/2000-virtual-list-structural/build
new file mode 100755
index 0000000..87d6acc
--- /dev/null
+++ b/test/2000-virtual-list-structural/build
@@ -0,0 +1,31 @@
+#!/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.
+
+# Stop on failure.
+set -e
+
+# Deref the symlink.
+mv src-ex/java/util/AbstractCollection.java src-ex/java/util/AbstractCollection.bak
+cp src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
+
+# Patch the copied version.
+patch src-ex/java/util/AbstractCollection.java AbstractCollection.patch
+
+DESUGAR=false ./default-build "$@"
+
+# restore the symlink
+rm src-ex/java/util/AbstractCollection.java
+mv src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
diff --git a/test/2000-virtual-list-structural/expected.txt b/test/2000-virtual-list-structural/expected.txt
new file mode 100644
index 0000000..9d3e1b6
--- /dev/null
+++ b/test/2000-virtual-list-structural/expected.txt
@@ -0,0 +1,5 @@
+List is: [a, b, c, d]
+List is: [1, 2, 3, 4]
+List is: [1, 2, 3, 4, xyz: 0, xyz: 1, xyz: 2, xyz: 3, xyz: 4, xyz: 5, xyz: 6, xyz: 7, xyz: 8, xyz: 9, xyz: 10, xyz: 11, xyz: 12, xyz: 13, xyz: 14, xyz: 15, xyz: 16, xyz: 17, xyz: 18, xyz: 19, xyz: 20, xyz: 21, xyz: 22, xyz: 23, xyz: 24, xyz: 25, xyz: 26, xyz: 27, xyz: 28, xyz: 29, xyz: 30, xyz: 31, xyz: 32, xyz: 33, xyz: 34, xyz: 35, xyz: 36, xyz: 37, xyz: 38, xyz: 39, xyz: 40, xyz: 41, xyz: 42, xyz: 43, xyz: 44, xyz: 45, xyz: 46, xyz: 47, xyz: 48, xyz: 49, xyz: 50, xyz: 51, xyz: 52, xyz: 53, xyz: 54, xyz: 55, xyz: 56, xyz: 57, xyz: 58, xyz: 59, xyz: 60, xyz: 61, xyz: 62, xyz: 63, xyz: 64, xyz: 65, xyz: 66, xyz: 67, xyz: 68, xyz: 69, xyz: 70, xyz: 71, xyz: 72, xyz: 73, xyz: 74, xyz: 75, xyz: 76, xyz: 77, xyz: 78, xyz: 79, xyz: 80, xyz: 81, xyz: 82, xyz: 83, xyz: 84, xyz: 85, xyz: 86, xyz: 87, xyz: 88, xyz: 89, xyz: 90, xyz: 91, xyz: 92, xyz: 93, xyz: 94, xyz: 95, xyz: 96, xyz: 97, xyz: 98, xyz: 99, xyz: 100, xyz: 101, xyz: 102, xyz: 103, xyz: 104, xyz: 105, xyz: 106, xyz: 107, xyz: 108, xyz: 109, xyz: 110, xyz: 111, xyz: 112, xyz: 113, xyz: 114, xyz: 115, xyz: 116, xyz: 117, xyz: 118, xyz: 119, xyz: 120, xyz: 121, xyz: 122, xyz: 123, xyz: 124, xyz: 125, xyz: 126, xyz: 127, xyz: 128, xyz: 129, xyz: 130, xyz: 131, xyz: 132, xyz: 133, xyz: 134, xyz: 135, xyz: 136, xyz: 137, xyz: 138, xyz: 139, xyz: 140, xyz: 141, xyz: 142, xyz: 143, xyz: 144, xyz: 145, xyz: 146, xyz: 147, xyz: 148, xyz: 149, xyz: 150, xyz: 151, xyz: 152, xyz: 153, xyz: 154, xyz: 155, xyz: 156, xyz: 157, xyz: 158, xyz: 159, xyz: 160, xyz: 161, xyz: 162, xyz: 163, xyz: 164, xyz: 165, xyz: 166, xyz: 167, xyz: 168, xyz: 169, xyz: 170, xyz: 171, xyz: 172, xyz: 173, xyz: 174, xyz: 175, xyz: 176, xyz: 177, xyz: 178, xyz: 179, xyz: 180, xyz: 181, xyz: 182, xyz: 183, xyz: 184, xyz: 185, xyz: 186, xyz: 187, xyz: 188, xyz: 189, xyz: 190, xyz: 191, xyz: 192, xyz: 193, xyz: 194, xyz: 195, xyz: 196, xyz: 197, xyz: 198, xyz: 199, xyz: 200, xyz: 201, xyz: 202, xyz: 203, xyz: 204, xyz: 205, xyz: 206, xyz: 207, xyz: 208, xyz: 209, xyz: 210, xyz: 211, xyz: 212, xyz: 213, xyz: 214, xyz: 215, xyz: 216, xyz: 217, xyz: 218, xyz: 219, xyz: 220, xyz: 221, xyz: 222, xyz: 223, xyz: 224, xyz: 225, xyz: 226, xyz: 227, xyz: 228, xyz: 229, xyz: 230, xyz: 231, xyz: 232, xyz: 233, xyz: 234, xyz: 235, xyz: 236, xyz: 237, xyz: 238, xyz: 239, xyz: 240, xyz: 241, xyz: 242, xyz: 243, xyz: 244, xyz: 245, xyz: 246, xyz: 247, xyz: 248, xyz: 249, xyz: 250, xyz: 251, xyz: 252, xyz: 253, xyz: 254, xyz: 255, xyz: 256, xyz: 257, xyz: 258, xyz: 259, xyz: 260, xyz: 261, xyz: 262, xyz: 263, xyz: 264, xyz: 265, xyz: 266, xyz: 267, xyz: 268, xyz: 269, xyz: 270, xyz: 271, xyz: 272, xyz: 273, xyz: 274, xyz: 275, xyz: 276, xyz: 277, xyz: 278, xyz: 279, xyz: 280, xyz: 281, xyz: 282, xyz: 283, xyz: 284, xyz: 285, xyz: 286, xyz: 287, xyz: 288, xyz: 289, xyz: 290, xyz: 291, xyz: 292, xyz: 293, xyz: 294, xyz: 295, xyz: 296, xyz: 297, xyz: 298, xyz: 299, xyz: 300, xyz: 301, xyz: 302, xyz: 303, xyz: 304, xyz: 305, xyz: 306, xyz: 307, xyz: 308, xyz: 309, xyz: 310, xyz: 311, xyz: 312, xyz: 313, xyz: 314, xyz: 315, xyz: 316, xyz: 317, xyz: 318, xyz: 319, xyz: 320, xyz: 321, xyz: 322, xyz: 323, xyz: 324, xyz: 325, xyz: 326, xyz: 327, xyz: 328, xyz: 329, xyz: 330, xyz: 331, xyz: 332, xyz: 333, xyz: 334, xyz: 335, xyz: 336, xyz: 337, xyz: 338, xyz: 339, xyz: 340, xyz: 341, xyz: 342, xyz: 343, xyz: 344, xyz: 345, xyz: 346, xyz: 347, xyz: 348, xyz: 349, xyz: 350, xyz: 351, xyz: 352, xyz: 353, xyz: 354, xyz: 355, xyz: 356, xyz: 357, xyz: 358, xyz: 359, xyz: 360, xyz: 361, xyz: 362, xyz: 363, xyz: 364, xyz: 365, xyz: 366, xyz: 367, xyz: 368, xyz: 369, xyz: 370, xyz: 371, xyz: 372, xyz: 373, xyz: 374, xyz: 375, xyz: 376, xyz: 377, xyz: 378, xyz: 379, xyz: 380, xyz: 381, xyz: 382, xyz: 383, xyz: 384, xyz: 385, xyz: 386, xyz: 387, xyz: 388, xyz: 389, xyz: 390, xyz: 391, xyz: 392, xyz: 393, xyz: 394, xyz: 395, xyz: 396, xyz: 397, xyz: 398, xyz: 399, xyz: 400, xyz: 401, xyz: 402, xyz: 403, xyz: 404, xyz: 405, xyz: 406, xyz: 407, xyz: 408, xyz: 409, xyz: 410, xyz: 411, xyz: 412, xyz: 413, xyz: 414, xyz: 415, xyz: 416, xyz: 417, xyz: 418, xyz: 419, xyz: 420, xyz: 421, xyz: 422, xyz: 423, xyz: 424, xyz: 425, xyz: 426, xyz: 427, xyz: 428, xyz: 429, xyz: 430, xyz: 431, xyz: 432, xyz: 433, xyz: 434, xyz: 435, xyz: 436, xyz: 437, xyz: 438, xyz: 439, xyz: 440, xyz: 441, xyz: 442, xyz: 443, xyz: 444, xyz: 445, xyz: 446, xyz: 447, xyz: 448, xyz: 449, xyz: 450, xyz: 451, xyz: 452, xyz: 453, xyz: 454, xyz: 455, xyz: 456, xyz: 457, xyz: 458, xyz: 459, xyz: 460, xyz: 461, xyz: 462, xyz: 463, xyz: 464, xyz: 465, xyz: 466, xyz: 467, xyz: 468, xyz: 469, xyz: 470, xyz: 471, xyz: 472, xyz: 473, xyz: 474, xyz: 475, xyz: 476, xyz: 477, xyz: 478, xyz: 479, xyz: 480, xyz: 481, xyz: 482, xyz: 483, xyz: 484, xyz: 485, xyz: 486, xyz: 487, xyz: 488, xyz: 489, xyz: 490, xyz: 491, xyz: 492, xyz: 493, xyz: 494, xyz: 495, xyz: 496, xyz: 497, xyz: 498, xyz: 499, xyz: 500, xyz: 501, xyz: 502, xyz: 503, xyz: 504, xyz: 505, xyz: 506, xyz: 507, xyz: 508, xyz: 509, xyz: 510, xyz: 511, xyz: 512, xyz: 513, xyz: 514, xyz: 515, xyz: 516, xyz: 517, xyz: 518, xyz: 519, xyz: 520, xyz: 521, xyz: 522, xyz: 523, xyz: 524, xyz: 525, xyz: 526, xyz: 527, xyz: 528, xyz: 529, xyz: 530, xyz: 531, xyz: 532, xyz: 533, xyz: 534, xyz: 535, xyz: 536, xyz: 537, xyz: 538, xyz: 539, xyz: 540, xyz: 541, xyz: 542, xyz: 543, xyz: 544, xyz: 545, xyz: 546, xyz: 547, xyz: 548, xyz: 549, xyz: 550, xyz: 551, xyz: 552, xyz: 553, xyz: 554, xyz: 555, xyz: 556, xyz: 557, xyz: 558, xyz: 559, xyz: 560, xyz: 561, xyz: 562, xyz: 563, xyz: 564, xyz: 565, xyz: 566, xyz: 567, xyz: 568, xyz: 569, xyz: 570, xyz: 571, xyz: 572, xyz: 573, xyz: 574, xyz: 575, xyz: 576, xyz: 577, xyz: 578, xyz: 579, xyz: 580, xyz: 581, xyz: 582, xyz: 583, xyz: 584, xyz: 585, xyz: 586, xyz: 587, xyz: 588, xyz: 589, xyz: 590, xyz: 591, xyz: 592, xyz: 593, xyz: 594, xyz: 595, xyz: 596, xyz: 597, xyz: 598, xyz: 599, xyz: 600, xyz: 601, xyz: 602, xyz: 603, xyz: 604, xyz: 605, xyz: 606, xyz: 607, xyz: 608, xyz: 609, xyz: 610, xyz: 611, xyz: 612, xyz: 613, xyz: 614, xyz: 615, xyz: 616, xyz: 617, xyz: 618, xyz: 619, xyz: 620, xyz: 621, xyz: 622, xyz: 623, xyz: 624, xyz: 625, xyz: 626, xyz: 627, xyz: 628, xyz: 629, xyz: 630, xyz: 631, xyz: 632, xyz: 633, xyz: 634, xyz: 635, xyz: 636, xyz: 637, xyz: 638, xyz: 639, xyz: 640, xyz: 641, xyz: 642, xyz: 643, xyz: 644, xyz: 645, xyz: 646, xyz: 647, xyz: 648, xyz: 649, xyz: 650, xyz: 651, xyz: 652, xyz: 653, xyz: 654, xyz: 655, xyz: 656, xyz: 657, xyz: 658, xyz: 659, xyz: 660, xyz: 661, xyz: 662, xyz: 663, xyz: 664, xyz: 665, xyz: 666, xyz: 667, xyz: 668, xyz: 669, xyz: 670, xyz: 671, xyz: 672, xyz: 673, xyz: 674, xyz: 675, xyz: 676, xyz: 677, xyz: 678, xyz: 679, xyz: 680, xyz: 681, xyz: 682, xyz: 683, xyz: 684, xyz: 685, xyz: 686, xyz: 687, xyz: 688, xyz: 689, xyz: 690, xyz: 691, xyz: 692, xyz: 693, xyz: 694, xyz: 695, xyz: 696, xyz: 697, xyz: 698, xyz: 699, xyz: 700, xyz: 701, xyz: 702, xyz: 703, xyz: 704, xyz: 705, xyz: 706, xyz: 707, xyz: 708, xyz: 709, xyz: 710, xyz: 711, xyz: 712, xyz: 713, xyz: 714, xyz: 715, xyz: 716, xyz: 717, xyz: 718, xyz: 719, xyz: 720, xyz: 721, xyz: 722, xyz: 723, xyz: 724, xyz: 725, xyz: 726, xyz: 727, xyz: 728, xyz: 729, xyz: 730, xyz: 731, xyz: 732, xyz: 733, xyz: 734, xyz: 735, xyz: 736, xyz: 737, xyz: 738, xyz: 739, xyz: 740, xyz: 741, xyz: 742, xyz: 743, xyz: 744, xyz: 745, xyz: 746, xyz: 747, xyz: 748, xyz: 749, xyz: 750, xyz: 751, xyz: 752, xyz: 753, xyz: 754, xyz: 755, xyz: 756, xyz: 757, xyz: 758, xyz: 759, xyz: 760, xyz: 761, xyz: 762, xyz: 763, xyz: 764, xyz: 765, xyz: 766, xyz: 767, xyz: 768, xyz: 769, xyz: 770, xyz: 771, xyz: 772, xyz: 773, xyz: 774, xyz: 775, xyz: 776, xyz: 777, xyz: 778, xyz: 779, xyz: 780, xyz: 781, xyz: 782, xyz: 783, xyz: 784, xyz: 785, xyz: 786, xyz: 787, xyz: 788, xyz: 789, xyz: 790, xyz: 791, xyz: 792, xyz: 793, xyz: 794, xyz: 795, xyz: 796, xyz: 797, xyz: 798, xyz: 799, xyz: 800, xyz: 801, xyz: 802, xyz: 803, xyz: 804, xyz: 805, xyz: 806, xyz: 807, xyz: 808, xyz: 809, xyz: 810, xyz: 811, xyz: 812, xyz: 813, xyz: 814, xyz: 815, xyz: 816, xyz: 817, xyz: 818, xyz: 819, xyz: 820, xyz: 821, xyz: 822, xyz: 823, xyz: 824, xyz: 825, xyz: 826, xyz: 827, xyz: 828, xyz: 829, xyz: 830, xyz: 831, xyz: 832, xyz: 833, xyz: 834, xyz: 835, xyz: 836, xyz: 837, xyz: 838, xyz: 839, xyz: 840, xyz: 841, xyz: 842, xyz: 843, xyz: 844, xyz: 845, xyz: 846, xyz: 847, xyz: 848, xyz: 849, xyz: 850, xyz: 851, xyz: 852, xyz: 853, xyz: 854, xyz: 855, xyz: 856, xyz: 857, xyz: 858, xyz: 859, xyz: 860, xyz: 861, xyz: 862, xyz: 863, xyz: 864, xyz: 865, xyz: 866, xyz: 867, xyz: 868, xyz: 869, xyz: 870, xyz: 871, xyz: 872, xyz: 873, xyz: 874, xyz: 875, xyz: 876, xyz: 877, xyz: 878, xyz: 879, xyz: 880, xyz: 881, xyz: 882, xyz: 883, xyz: 884, xyz: 885, xyz: 886, xyz: 887, xyz: 888, xyz: 889, xyz: 890, xyz: 891, xyz: 892, xyz: 893, xyz: 894, xyz: 895, xyz: 896, xyz: 897, xyz: 898, xyz: 899, xyz: 900, xyz: 901, xyz: 902, xyz: 903, xyz: 904, xyz: 905, xyz: 906, xyz: 907, xyz: 908, xyz: 909, xyz: 910, xyz: 911, xyz: 912, xyz: 913, xyz: 914, xyz: 915, xyz: 916, xyz: 917, xyz: 918, xyz: 919, xyz: 920, xyz: 921, xyz: 922, xyz: 923, xyz: 924, xyz: 925, xyz: 926, xyz: 927, xyz: 928, xyz: 929, xyz: 930, xyz: 931, xyz: 932, xyz: 933, xyz: 934, xyz: 935, xyz: 936, xyz: 937, xyz: 938, xyz: 939, xyz: 940, xyz: 941, xyz: 942, xyz: 943, xyz: 944, xyz: 945, xyz: 946, xyz: 947, xyz: 948, xyz: 949, xyz: 950, xyz: 951, xyz: 952, xyz: 953, xyz: 954, xyz: 955, xyz: 956, xyz: 957, xyz: 958, xyz: 959, xyz: 960, xyz: 961, xyz: 962, xyz: 963, xyz: 964, xyz: 965, xyz: 966, xyz: 967, xyz: 968, xyz: 969, xyz: 970, xyz: 971, xyz: 972, xyz: 973, xyz: 974, xyz: 975, xyz: 976, xyz: 977, xyz: 978, xyz: 979, xyz: 980, xyz: 981, xyz: 982, xyz: 983, xyz: 984, xyz: 985, xyz: 986, xyz: 987, xyz: 988, xyz: 989, xyz: 990, xyz: 991, xyz: 992, xyz: 993, xyz: 994, xyz: 995, xyz: 996, xyz: 997, xyz: 998, xyz: 999]
+List is: [1, 2, 3, 4]
+List is: [a, b, c, d]
diff --git a/test/2000-virtual-list-structural/info.txt b/test/2000-virtual-list-structural/info.txt
new file mode 100644
index 0000000..606c984
--- /dev/null
+++ b/test/2000-virtual-list-structural/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition can add new virtual methods and fields.
diff --git a/test/2000-virtual-list-structural/run b/test/2000-virtual-list-structural/run
new file mode 100755
index 0000000..0d41632
--- /dev/null
+++ b/test/2000-virtual-list-structural/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# invoke-virtual-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of vtable methods. Since this test
+# changes the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/2000-virtual-list-structural/src-ex/java/util/AbstractCollection.java b/test/2000-virtual-list-structural/src-ex/java/util/AbstractCollection.java
new file mode 120000
index 0000000..a30fbdc
--- /dev/null
+++ b/test/2000-virtual-list-structural/src-ex/java/util/AbstractCollection.java
@@ -0,0 +1 @@
+../../../../../../libcore/ojluni/src/main/java/java/util/AbstractCollection.java
\ No newline at end of file
diff --git a/test/2000-virtual-list-structural/src/Main.java b/test/2000-virtual-list-structural/src/Main.java
new file mode 100644
index 0000000..b2a42c2
--- /dev/null
+++ b/test/2000-virtual-list-structural/src/Main.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import art.*;
+import java.util.*;
+import java.lang.invoke.*;
+import java.io.*;
+import java.util.zip.*;
+
+public class Main {
+ public static final String TEST_NAME = "2000-virtual-list-structural";
+ public static final boolean PRINT_COUNT = false;
+ public static MethodHandles.Lookup lookup = MethodHandles.publicLookup();
+ public static MethodHandle getcnt;
+ public static MethodHandle get_total_cnt;
+ public static void GetHandles() throws Throwable {
+ getcnt = lookup.findGetter(AbstractCollection.class, "cnt", Integer.TYPE);
+ get_total_cnt = lookup.findStaticGetter(AbstractCollection.class, "TOTAL_COUNT", Integer.TYPE);
+ }
+
+ public static byte[] GetDexBytes() throws Throwable {
+ String jar_loc = System.getenv("DEX_LOCATION") + "/" + TEST_NAME + "-ex.jar";
+ try (ZipFile zip = new ZipFile(new File(jar_loc))) {
+ ZipEntry entry = zip.getEntry("classes.dex");
+ try (InputStream is = zip.getInputStream(entry)) {
+ byte[] res = new byte[(int)entry.getSize()];
+ is.read(res);
+ return res;
+ }
+ }
+ }
+ public static void PrintListAndData(AbstractCollection<String> c) throws Throwable {
+ if (PRINT_COUNT) {
+ System.out.println("List is: " + c + " count = " + getcnt.invoke(c) + " TOTAL_COUNT = " + get_total_cnt.invoke());
+ } else {
+ System.out.println("List is: " + c);
+ }
+ }
+ public static void main(String[] args) throws Throwable {
+ AbstractCollection<String> l1 = (AbstractCollection<String>)Arrays.asList("a", "b", "c", "d");
+ AbstractCollection<String> l2 = new ArrayList<>();
+ l2.add("1");
+ l2.add("2");
+ l2.add("3");
+ l2.add("4");
+ Redefinition.doCommonStructuralClassRedefinition(AbstractCollection.class, GetDexBytes());
+ GetHandles();
+ AbstractCollection<String> l3 = new HashSet<>(l2);
+ AbstractCollection<String> l4 = new LinkedList<>(l1);
+ PrintListAndData(l1);
+ PrintListAndData(l2);
+ for (int i = 0; i < 1000; i++) {
+ l2.add("xyz: " + i);
+ }
+ PrintListAndData(l2);
+ PrintListAndData(l3);
+ PrintListAndData(l4);
+ CheckLE(getcnt.invoke(l1), get_total_cnt.invoke());
+ CheckLE(getcnt.invoke(l2), get_total_cnt.invoke());
+ CheckLE(getcnt.invoke(l3), get_total_cnt.invoke());
+ CheckLE(getcnt.invoke(l4), get_total_cnt.invoke());
+ CheckEQ(getcnt.invoke(l1), 0);
+ CheckLE(getcnt.invoke(l2), 0);
+ CheckLE(getcnt.invoke(l1), getcnt.invoke(l2));
+ CheckLE(getcnt.invoke(l1), getcnt.invoke(l3));
+ CheckLE(getcnt.invoke(l1), getcnt.invoke(l4));
+ CheckLE(getcnt.invoke(l2), getcnt.invoke(l3));
+ CheckLE(getcnt.invoke(l2), getcnt.invoke(l4));
+ CheckLE(getcnt.invoke(l3), getcnt.invoke(l4));
+ }
+ public static void CheckEQ(Object a, int b) {
+ CheckEQ(((Integer)a).intValue(), b);
+ }
+ public static void CheckLE(Object a, Object b) {
+ CheckLE(((Integer)a).intValue(), ((Integer)b).intValue());
+ }
+ public static void CheckEQ(int a, int b) {
+ if (a != b) {
+ throw new Error(a + " is not equal to " + b);
+ }
+ }
+ public static void CheckLE(int a, int b) {
+ if (!(a <= b)) {
+ throw new Error(a + " is not less than or equal to " + b);
+ }
+ }
+}
diff --git a/test/2000-virtual-list-structural/src/art/Redefinition.java b/test/2000-virtual-list-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2000-virtual-list-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2001-virtual-structural-multithread/expected.txt b/test/2001-virtual-structural-multithread/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/expected.txt
diff --git a/test/2001-virtual-structural-multithread/info.txt b/test/2001-virtual-structural-multithread/info.txt
new file mode 100644
index 0000000..3e5291d
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/info.txt
@@ -0,0 +1,4 @@
+Tests structural redefinition with multiple threads.
+
+Tests that using the structural redefinition while concurrently loading and using a subtype of
+the class being redefined doesn't cause any unexpected problems.
diff --git a/test/2001-virtual-structural-multithread/run b/test/2001-virtual-structural-multithread/run
new file mode 100755
index 0000000..421f7b0
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# iget-object-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of fields. Since this test changes
+# the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/2001-virtual-structural-multithread/src-art/Main.java b/test/2001-virtual-structural-multithread/src-art/Main.java
new file mode 100644
index 0000000..618cdcd
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/src-art/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.Test2001.run();
+ }
+}
diff --git a/test/2001-virtual-structural-multithread/src-art/art/Redefinition.java b/test/2001-virtual-structural-multithread/src-art/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/src-art/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2001-virtual-structural-multithread/src-art/art/Test2001.java b/test/2001-virtual-structural-multithread/src-art/art/Test2001.java
new file mode 100644
index 0000000..e6ee0ce
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/src-art/art/Test2001.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2016 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 dalvik.system.InMemoryDexClassLoader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Supplier;
+
+public class Test2001 {
+ private static final int NUM_THREADS = 20;
+
+ public static class Transform {
+ public String greetingEnglish;
+
+ public Transform() {
+ this.greetingEnglish = "Hello";
+ }
+
+ public String sayHi() {
+ return greetingEnglish + " from " + Thread.currentThread().getName();
+ }
+ }
+
+ /**
+ * base64 encoded class/dex file for
+ * public static class Transform {
+ * public String greetingEnglish;
+ * public String greetingFrench;
+ * public String greetingDanish;
+ * public String greetingJapanese;
+ *
+ * public Transform() {
+ * this.greetingEnglish = "Hello World";
+ * this.greetingFrench = "Bonjour le Monde";
+ * this.greetingDanish = "Hej Verden";
+ * this.greetingJapanese = "こんにちは世界";
+ * }
+ * public String sayHi() {
+ * return sayHiEnglish() + ", " + sayHiFrench() + ", " + sayHiDanish() + ", " + sayHiJapanese() + " from " + Thread.currentThread().getName();
+ * }
+ * public String sayHiEnglish() {
+ * return greetingEnglish;
+ * }
+ * public String sayHiDanish() {
+ * return greetingDanish;
+ * }
+ * public String sayHiJapanese() {
+ * return greetingJapanese;
+ * }
+ * public String sayHiFrench() {
+ * return greetingFrench;
+ * }
+ * }
+ */
+ private static final byte[] DEX_BYTES =
+ Base64.getDecoder()
+ .decode(
+ "ZGV4CjAzNQCnKPY06VRa4aM/zFW0MYLmRxT/NtXxD/H4BgAAcAAAAHhWNBIAAAAAAAAAADQGAAAl"
+ + "AAAAcAAAAAkAAAAEAQAABAAAACgBAAAEAAAAWAEAAAwAAAB4AQAAAQAAANgBAAAABQAA+AEAAEoD"
+ + "AABSAwAAVgMAAF4DAABwAwAAfAMAAIkDAACMAwAAkAMAAKoDAAC6AwAA3gMAAP4DAAASBAAAJgQA"
+ + "AEEEAABVBAAAZAQAAG8EAAByBAAAfwQAAIcEAACWBAAAnwQAAK8EAADABAAA0AQAAOIEAADoBAAA"
+ + "7wQAAPwEAAAKBQAAFwUAACYFAAAwBQAANwUAAMUFAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO"
+ + "AAAADwAAABIAAAAGAAAABQAAAAAAAAAHAAAABgAAAEQDAAAGAAAABwAAAAAAAAASAAAACAAAAAAA"
+ + "AAAAAAUAFwAAAAAABQAYAAAAAAAFABkAAAAAAAUAGgAAAAAAAwACAAAAAAAAABwAAAAAAAAAHQAA"
+ + "AAAAAAAeAAAAAAAAAB8AAAAAAAAAIAAAAAQAAwACAAAABgADAAIAAAAGAAEAFAAAAAYAAAAhAAAA"
+ + "BwACABUAAAAHAAAAFgAAAAAAAAABAAAABAAAAAAAAAAQAAAAJAYAAOsFAAAAAAAABwABAAIAAAAt"
+ + "AwAAQQAAAG4QAwAGAAwAbhAEAAYADAFuEAIABgAMAm4QBQAGAAwDcQAKAAAADARuEAsABAAMBCIF"
+ + "BgBwEAcABQBuIAgABQAaAAEAbiAIAAUAbiAIABUAbiAIAAUAbiAIACUAbiAIAAUAbiAIADUAGgAA"
+ + "AG4gCAAFAG4gCABFAG4QCQAFAAwAEQAAAAIAAQAAAAAAMQMAAAMAAABUEAAAEQAAAAIAAQAAAAAA"
+ + "NQMAAAMAAABUEAEAEQAAAAIAAQAAAAAAOQMAAAMAAABUEAIAEQAAAAIAAQAAAAAAPQMAAAMAAABU"
+ + "EAMAEQAAAAIAAQABAAAAJAMAABQAAABwEAYAAQAaAAUAWxABABoAAwBbEAIAGgAEAFsQAAAaACQA"
+ + "WxADAA4ACwAOPEtLS0sAEgAOABgADgAVAA4AHgAOABsADgAAAAABAAAABQAGIGZyb20gAAIsIAAG"
+ + "PGluaXQ+ABBCb25qb3VyIGxlIE1vbmRlAApIZWogVmVyZGVuAAtIZWxsbyBXb3JsZAABTAACTEwA"
+ + "GExhcnQvVGVzdDIwMDEkVHJhbnNmb3JtOwAOTGFydC9UZXN0MjAwMTsAIkxkYWx2aWsvYW5ub3Rh"
+ + "dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph"
+ + "dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp"
+ + "bGRlcjsAEkxqYXZhL2xhbmcvVGhyZWFkOwANVGVzdDIwMDEuamF2YQAJVHJhbnNmb3JtAAFWAAth"
+ + "Y2Nlc3NGbGFncwAGYXBwZW5kAA1jdXJyZW50VGhyZWFkAAdnZXROYW1lAA5ncmVldGluZ0Rhbmlz"
+ + "aAAPZ3JlZXRpbmdFbmdsaXNoAA5ncmVldGluZ0ZyZW5jaAAQZ3JlZXRpbmdKYXBhbmVzZQAEbmFt"
+ + "ZQAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gAC3NheUhpRnJlbmNoAA1zYXlIaUph"
+ + "cGFuZXNlAAh0b1N0cmluZwAFdmFsdWUAiwF+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWci"
+ + "LCJoYXMtY2hlY2tzdW1zIjpmYWxzZSwibWluLWFwaSI6MSwic2hhLTEiOiJmNjJiOGNlNmEwNTkw"
+ + "MDU0ZWYzNGExYWVkZTcwYjQ2NjY4ZThiNDlmIiwidmVyc2lvbiI6IjIuMC4xLWRldiJ9AAfjgZPj"
+ + "gpPjgavjgaHjga/kuJbnlYwAAgIBIhgBAgMCEwQJGxcRAAQBBQABAQEBAQEBAIGABOwFAQH4AwEB"
+ + "jAUBAaQFAQG8BQEB1AUAAAAAAAAAAgAAANwFAADiBQAAGAYAAAAAAAAAAAAAAAAAABAAAAAAAAAA"
+ + "AQAAAAAAAAABAAAAJQAAAHAAAAACAAAACQAAAAQBAAADAAAABAAAACgBAAAEAAAABAAAAFgBAAAF"
+ + "AAAADAAAAHgBAAAGAAAAAQAAANgBAAABIAAABgAAAPgBAAADIAAABgAAACQDAAABEAAAAQAAAEQD"
+ + "AAACIAAAJQAAAEoDAAAEIAAAAgAAANwFAAAAIAAAAQAAAOsFAAADEAAAAgAAABQGAAAGIAAAAQAA"
+ + "ACQGAAAAEAAAAQAAADQGAAA=");
+
+ /*
+ * base64 encoded class/dex file for
+ package art;
+ import java.util.function.Supplier;
+ public class SubTransform extends art.Test2001.Transform implements Supplier<String> {
+ public SubTransform() {
+ super();
+ }
+ public String get() {
+ return "from SUBCLASS: " + super.sayHi();
+ }
+ }
+ */
+ private static final byte[] SUB_DEX_BYTES =
+ Base64.getDecoder()
+ .decode(
+ "ZGV4CjAzNQBawzkIDf9khFw00md41U4vIqRuhqBTjM+0BAAAcAAAAHhWNBIAAAAAAAAAAPwDAAAV"
+ + "AAAAcAAAAAgAAADEAAAABAAAAOQAAAAAAAAAAAAAAAgAAAAUAQAAAQAAAFQBAABAAwAAdAEAAAIC"
+ + "AAAKAgAADgIAABECAAAVAgAAKQIAAEMCAABiAgAAdgIAAIoCAAClAgAAxAIAAOMCAAD2AgAA+QIA"
+ + "AAEDAAASAwAAFwMAAB4DAAAoAwAALwMAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAANAAAA"
+ + "AgAAAAMAAAAAAAAAAgAAAAQAAAAAAAAAAwAAAAUAAAD8AQAADQAAAAcAAAAAAAAAAAADAAAAAAAA"
+ + "AAAAEAAAAAAAAQAQAAAAAQADAAAAAAABAAEAEQAAAAUAAwAAAAAABQACAA4AAAAFAAEAEgAAAAAA"
+ + "AAABAAAAAQAAAPQBAAAMAAAA7AMAAMsDAAAAAAAAAgABAAEAAADpAQAABQAAAG4QAgABAAwAEQAA"
+ + "AAQAAQACAAAA7QEAABYAAABvEAQAAwAMACIBBQBwEAUAAQAaAg8AbiAGACEAbiAGAAEAbhAHAAEA"
+ + "DAARAAEAAQABAAAA5AEAAAQAAABwEAMAAAAOAAYADjwABAAOAAkADgAAAAABAAAABgAAAAEAAAAE"
+ + "AAY8aW5pdD4AAj47AAFMAAJMTAASTGFydC9TdWJUcmFuc2Zvcm07ABhMYXJ0L1Rlc3QyMDAxJFRy"
+ + "YW5zZm9ybTsAHUxkYWx2aWsvYW5ub3RhdGlvbi9TaWduYXR1cmU7ABJMamF2YS9sYW5nL09iamVj"
+ + "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwAdTGphdmEv"
+ + "dXRpbC9mdW5jdGlvbi9TdXBwbGllcjsAHUxqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXI8ABFT"
+ + "dWJUcmFuc2Zvcm0uamF2YQABVgAGYXBwZW5kAA9mcm9tIFNVQkNMQVNTOiAAA2dldAAFc2F5SGkA"
+ + "CHRvU3RyaW5nAAV2YWx1ZQCLAX5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsImhhcy1j"
+ + "aGVja3N1bXMiOmZhbHNlLCJtaW4tYXBpIjoxLCJzaGEtMSI6ImY2MmI4Y2U2YTA1OTAwNTRlZjM0"
+ + "YTFhZWRlNzBiNDY2NjhlOGI0OWYiLCJ2ZXJzaW9uIjoiMi4wLjEtZGV2In0AAgIBExwEFwUXCxcI"
+ + "FwEAAAECAIGABMwDAcEg9AIBAZADAAAAAAAAAQAAAL0DAADkAwAAAAAAAAAAAAAAAAAADwAAAAAA"
+ + "AAABAAAAAAAAAAEAAAAVAAAAcAAAAAIAAAAIAAAAxAAAAAMAAAAEAAAA5AAAAAUAAAAIAAAAFAEA"
+ + "AAYAAAABAAAAVAEAAAEgAAADAAAAdAEAAAMgAAADAAAA5AEAAAEQAAACAAAA9AEAAAIgAAAVAAAA"
+ + "AgIAAAQgAAABAAAAvQMAAAAgAAABAAAAywMAAAMQAAACAAAA4AMAAAYgAAABAAAA7AMAAAAQAAAB"
+ + "AAAA/AMAAA==");
+
+ public static void run() throws Exception {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest();
+ }
+
+ public static Supplier<String> mkTransform() {
+ try {
+ return (Supplier<String>)
+ (new InMemoryDexClassLoader(
+ ByteBuffer.wrap(SUB_DEX_BYTES), Test2001.class.getClassLoader())
+ .loadClass("art.SubTransform")
+ .newInstance());
+ } catch (Exception e) {
+ return () -> {
+ return e.toString();
+ };
+ }
+ }
+
+ public static final class MyThread extends Thread {
+ public MyThread(CountDownLatch delay, int id) {
+ super("Thread: " + id);
+ this.thr_id = id;
+ this.results = new ArrayList<>(1000);
+ this.finish = false;
+ this.delay = delay;
+ }
+
+ public void run() {
+ delay.countDown();
+ while (!finish) {
+ Supplier<String> t = mkTransform();
+ results.add(t.get());
+ }
+ }
+
+ public void finish() throws Exception {
+ finish = true;
+ this.join();
+ }
+
+ public void Check() throws Exception {
+ for (String s : results) {
+ if (!s.equals("from SUBCLASS: Hello from " + getName())
+ && !s.equals("from SUBCLASS: Hello, null, null, null from " + getName())
+ && !s.equals(
+ "from SUBCLASS: Hello World, Bonjour le Monde, Hej Verden, こんにちは世界 from "
+ + getName())) {
+ System.out.println("FAIL " + thr_id + ": Unexpected result: " + s);
+ }
+ }
+ }
+
+ public ArrayList<String> results;
+ public volatile boolean finish;
+ public int thr_id;
+ public CountDownLatch delay;
+ }
+
+ public static MyThread[] startThreads(int num_threads) throws Exception {
+ CountDownLatch cdl = new CountDownLatch(num_threads);
+ MyThread[] res = new MyThread[num_threads];
+ for (int i = 0; i < num_threads; i++) {
+ res[i] = new MyThread(cdl, i);
+ res[i].start();
+ }
+ cdl.await();
+ return res;
+ }
+
+ public static void finishThreads(MyThread[] thrs) throws Exception {
+ for (MyThread t : thrs) {
+ t.finish();
+ }
+ for (MyThread t : thrs) {
+ t.Check();
+ }
+ }
+
+ public static void doTest() throws Exception {
+ MyThread[] threads = startThreads(NUM_THREADS);
+ Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+ finishThreads(threads);
+ }
+}
diff --git a/test/2001-virtual-structural-multithread/src/Main.java b/test/2001-virtual-structural-multithread/src/Main.java
new file mode 100644
index 0000000..89b8557
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/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 {
+ System.out.println("FAIL: Test is only for art!");
+ }
+}
diff --git a/test/2002-virtual-structural-initializing/expected.txt b/test/2002-virtual-structural-initializing/expected.txt
new file mode 100644
index 0000000..c1c8a70
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/expected.txt
@@ -0,0 +1 @@
+Initialized Static Hello
diff --git a/test/2002-virtual-structural-initializing/info.txt b/test/2002-virtual-structural-initializing/info.txt
new file mode 100644
index 0000000..3e5291d
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/info.txt
@@ -0,0 +1,4 @@
+Tests structural redefinition with multiple threads.
+
+Tests that using the structural redefinition while concurrently loading and using a subtype of
+the class being redefined doesn't cause any unexpected problems.
diff --git a/test/2002-virtual-structural-initializing/run b/test/2002-virtual-structural-initializing/run
new file mode 100755
index 0000000..421f7b0
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# iget-object-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of fields. Since this test changes
+# the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/2002-virtual-structural-initializing/src-art/Main.java b/test/2002-virtual-structural-initializing/src-art/Main.java
new file mode 100644
index 0000000..a0aab42
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/src-art/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.Test2002.run();
+ }
+}
diff --git a/test/2002-virtual-structural-initializing/src-art/art/Redefinition.java b/test/2002-virtual-structural-initializing/src-art/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/src-art/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2002-virtual-structural-initializing/src-art/art/Test2002.java b/test/2002-virtual-structural-initializing/src-art/art/Test2002.java
new file mode 100644
index 0000000..f91e3f7
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/src-art/art/Test2002.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 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 dalvik.system.InMemoryDexClassLoader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Supplier;
+
+public class Test2002 {
+ public static final CountDownLatch start_latch = new CountDownLatch(1);
+ public static final CountDownLatch finish_latch = new CountDownLatch(1);
+ public static class Transform {
+ public Transform() { }
+
+ public String sayHi() {
+ return "Hi";
+ }
+ }
+
+ /**
+ * base64 encoded class/dex file for
+ * public static class Transform {
+ * public String greeting;
+ *
+ * public Transform() {
+ * greeting = "Hello";
+ * }
+ * public String sayHi() {
+ * return greeting;
+ * }
+ * }
+ */
+ private static final byte[] DEX_BYTES =
+ Base64.getDecoder()
+ .decode(
+"ZGV4CjAzNQBlpDFxr5PhCBfCyN+GZYuYQvSqtTEESU3oAwAAcAAAAHhWNBIAAAAAAAAAADADAAAS" +
+"AAAAcAAAAAcAAAC4AAAAAgAAANQAAAABAAAA7AAAAAMAAAD0AAAAAQAAAAwBAAC8AgAALAEAAHAB" +
+"AAB4AQAAfwEAAIIBAACcAQAArAEAANABAADwAQAABAIAABgCAAAnAgAAMgIAADUCAABCAgAATAIA" +
+"AFICAABZAgAAYAIAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAsAAAACAAAABQAAAAAAAAALAAAA" +
+"BgAAAAAAAAAAAAUADQAAAAAAAQAAAAAAAAAAAA8AAAAEAAEAAAAAAAAAAAABAAAABAAAAAAAAAAJ" +
+"AAAAIAMAAP0CAAAAAAAAAgABAAAAAABqAQAAAwAAAFQQAAARAAAAAgABAAEAAABkAQAACAAAAHAQ" +
+"AgABABoAAQBbEAAADgAGAA48SwAKAA4AAAAGPGluaXQ+AAVIZWxsbwABTAAYTGFydC9UZXN0MjAw" +
+"MiRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QyMDAyOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2lu" +
+"Z0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09iamVj" +
+"dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwANVGVzdDIwMDIuamF2YQAJVHJhbnNmb3JtAAFWAAthY2Nl" +
+"c3NGbGFncwAIZ3JlZXRpbmcABG5hbWUABXNheUhpAAV2YWx1ZQCLAX5+RDh7ImNvbXBpbGF0aW9u" +
+"LW1vZGUiOiJkZWJ1ZyIsImhhcy1jaGVja3N1bXMiOmZhbHNlLCJtaW4tYXBpIjoxLCJzaGEtMSI6" +
+"ImY2MmI4Y2U2YTA1OTAwNTRlZjM0YTFhZWRlNzBiNDY2NjhlOGI0OWYiLCJ2ZXJzaW9uIjoiMi4w" +
+"LjEtZGV2In0AAgIBEBgBAgMCDAQJDhcKAAEBAQABAIGABMQCAQGsAgAAAAAAAAACAAAA7gIAAPQC" +
+"AAAUAwAAAAAAAAAAAAAAAAAADwAAAAAAAAABAAAAAAAAAAEAAAASAAAAcAAAAAIAAAAHAAAAuAAA" +
+"AAMAAAACAAAA1AAAAAQAAAABAAAA7AAAAAUAAAADAAAA9AAAAAYAAAABAAAADAEAAAEgAAACAAAA" +
+"LAEAAAMgAAACAAAAZAEAAAIgAAASAAAAcAEAAAQgAAACAAAA7gIAAAAgAAABAAAA/QIAAAMQAAAC" +
+"AAAAEAMAAAYgAAABAAAAIAMAAAAQAAABAAAAMAMAAA==");
+
+ /*
+ * base64 encoded class/dex file for
+ package art;
+ import java.util.function.Supplier;
+ import java.util.concurrent.CountDownLatch;
+
+ public class SubTransform extends art.Test2002.Transform implements Supplier<String> {
+ public static final String staticId;
+ static {
+ String res = null;
+ try {
+ Test2002.start_latch.countDown();
+ Test2002.finish_latch.await();
+ res = "Initialized Static";
+ } catch (Exception e) {
+ res = e.toString();
+ }
+ staticId = res;
+ }
+ public SubTransform() {
+ super();
+ }
+ public String get() {
+ return SubTransform.staticId + " " + sayHi();
+ }
+ }
+ */
+ private static final byte[] SUB_DEX_BYTES =
+ Base64.getDecoder()
+ .decode(
+"ZGV4CjAzNQB0BhXQtGTKXAGE/UzeevPgeNK7UrQJRJkoBgAAcAAAAHhWNBIAAAAAAAAAAGQFAAAf" +
+"AAAAcAAAAAsAAADsAAAABAAAABgBAAADAAAASAEAAAwAAABgAQAAAQAAAMABAABIBAAA4AEAAM4C" +
+"AADRAgAA2wIAAOMCAADnAgAA+wIAAP4CAAACAwAAFgMAADADAABAAwAAXwMAAHYDAACKAwAAngMA" +
+"ALkDAADgAwAA/wMAAB4EAAAxBAAANAQAADwEAABDBAAATgQAAFwEAABhBAAAaAQAAHUEAAB/BAAA" +
+"iQQAAJAEAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8AAAAQAAAAEwAAAAUAAAAF" +
+"AAAAAAAAAAUAAAAGAAAAAAAAAAYAAAAHAAAAyAIAABMAAAAKAAAAAAAAAAAABgAbAAAAAgAIABcA" +
+"AAACAAgAGgAAAAAAAwABAAAAAAADAAIAAAAAAAAAGAAAAAAAAQAYAAAAAAABABkAAAABAAMAAgAA" +
+"AAQAAQAcAAAABwADAAIAAAAHAAIAFAAAAAcAAQAcAAAACAADABUAAAAIAAMAFgAAAAAAAAABAAAA" +
+"AQAAAMACAAASAAAAVAUAACwFAAAAAAAAAgABAAEAAAC1AgAABQAAAG4QAwABAAwAEQAAAAQAAQAC" +
+"AAAAuQIAABsAAABiAAAAbhAEAAMADAEiAgcAcBAHAAIAbiAIAAIAGgAAAG4gCAACAG4gCAASAG4Q" +
+"CQACAAwAEQAAAAEAAAABAAEApAIAABYAAAAAAGIAAgBuEAsAAABiAAEAbhAKAAAAGgAEACgGDQBu" +
+"EAYAAAAMAGkAAAAOAAEAAAAMAAEAAQEEDgEAAQABAAAAsAIAAAQAAABwEAUAAAAOAAgADh9aWi8b" +
+"HkwtABMADjwABQAOABYADgAAAAABAAAACQAAAAEAAAAGAAEgAAg8Y2xpbml0PgAGPGluaXQ+AAI+" +
+"OwASSW5pdGlhbGl6ZWQgU3RhdGljAAFMAAJMTAASTGFydC9TdWJUcmFuc2Zvcm07ABhMYXJ0L1Rl" +
+"c3QyMDAyJFRyYW5zZm9ybTsADkxhcnQvVGVzdDIwMDI7AB1MZGFsdmlrL2Fubm90YXRpb24vU2ln" +
+"bmF0dXJlOwAVTGphdmEvbGFuZy9FeGNlcHRpb247ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh" +
+"L2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwAlTGphdmEvdXRpbC9jb25j" +
+"dXJyZW50L0NvdW50RG93bkxhdGNoOwAdTGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjsAHUxq" +
+"YXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXI8ABFTdWJUcmFuc2Zvcm0uamF2YQABVgAGYXBwZW5k" +
+"AAVhd2FpdAAJY291bnREb3duAAxmaW5pc2hfbGF0Y2gAA2dldAAFc2F5SGkAC3N0YXJ0X2xhdGNo" +
+"AAhzdGF0aWNJZAAIdG9TdHJpbmcABXZhbHVlAIsBfn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRl" +
+"YnVnIiwiaGFzLWNoZWNrc3VtcyI6ZmFsc2UsIm1pbi1hcGkiOjEsInNoYS0xIjoiZjYyYjhjZTZh" +
+"MDU5MDA1NGVmMzRhMWFlZGU3MGI0NjY2OGU4YjQ5ZiIsInZlcnNpb24iOiIyLjAuMS1kZXYifQAC" +
+"AwEdHAQXCBcRFw0XAwEAAgIAGQCIgATEBAGBgASMBQLBIOADAQH8AwAAAAAAAQAAAB4FAABMBQAA" +
+"AAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAfAAAAcAAAAAIAAAALAAAA7AAAAAMAAAAE" +
+"AAAAGAEAAAQAAAADAAAASAEAAAUAAAAMAAAAYAEAAAYAAAABAAAAwAEAAAEgAAAEAAAA4AEAAAMg" +
+"AAAEAAAApAIAAAEQAAACAAAAwAIAAAIgAAAfAAAAzgIAAAQgAAABAAAAHgUAAAAgAAABAAAALAUA" +
+"AAMQAAACAAAASAUAAAYgAAABAAAAVAUAAAAQAAABAAAAZAUAAA==");
+
+ public static void run() throws Exception {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest();
+ }
+
+ public static Supplier<String> mkTransform() {
+ try {
+ return (Supplier<String>)
+ (new InMemoryDexClassLoader(
+ ByteBuffer.wrap(SUB_DEX_BYTES), Test2002.class.getClassLoader())
+ .loadClass("art.SubTransform")
+ .newInstance());
+ } catch (Exception e) {
+ return () -> {
+ return e.toString();
+ };
+ }
+ }
+
+ public static void doTest() throws Exception {
+ Thread t = new Thread(() -> {
+ Supplier<String> s = mkTransform();
+ System.out.println(s.get());
+ });
+ t.start();
+ start_latch.await();
+ Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+ finish_latch.countDown();
+ t.join();
+ }
+}
diff --git a/test/2002-virtual-structural-initializing/src/Main.java b/test/2002-virtual-structural-initializing/src/Main.java
new file mode 100644
index 0000000..89b8557
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/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 {
+ System.out.println("FAIL: Test is only for art!");
+ }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 8840de5..c5115e9 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1150,7 +1150,11 @@
"1995-final-virtual-structural-multithread",
"1996-final-override-virtual-structural",
"1997-structural-shadow-method",
- "1998-structural-shadow-field"
+ "1998-structural-shadow-field",
+ "1999-virtual-structural",
+ "2000-virtual-list-structural",
+ "2001-virtual-structural-multithread",
+ "2002-virtual-structural-initializing"
],
"variant": "jvm",
"description": ["Doesn't run on RI."]