Initial support for adding virtuals with structural redefinition
Initial implementation of adding virtual methods and non-static fields
using structural redefinition. Currently this is limited to 'final',
non-finalizable classes. These restrictions will be removed or
loosened in the future.
All non-collected instances of the redefined class will be made
obsolete and reallocated. This can cause significant GC load.
This feature does not work with any of the -quick opcodes and should
only be used with dex files that haven't undergone dex2dex
compilation (that is --debuggable and BCP dex files).
Test: ./test.py --host
Bug: 134162467
Bug: 144168550
Change-Id: Ia401d97395cfe498eb849a661ea9a900dfaa6da3
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
index e7306ba..37ae951 100644
--- a/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -26,6 +26,7 @@
defaults: ["art_defaults"],
host_supported: true,
srcs: [
+ "alloc_manager.cc",
"deopt_manager.cc",
"events.cc",
"fixed_up_dex_file.cc",
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 665fa9f..4ce376f 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -40,6 +40,7 @@
#include "jvmti.h"
+#include "alloc_manager.h"
#include "art_jvmti.h"
#include "base/logging.h" // For gLogVerbosity.
#include "base/mutex.h"
@@ -79,6 +80,7 @@
// These should never be null.
EventHandler* gEventHandler;
DeoptManager* gDeoptManager;
+AllocationManager* gAllocManager;
#define ENSURE_NON_NULL(n) \
do { \
@@ -1497,6 +1499,7 @@
extern "C" bool ArtPlugin_Initialize() {
art::Runtime* runtime = art::Runtime::Current();
+ gAllocManager = new AllocationManager;
gDeoptManager = new DeoptManager;
gEventHandler = new EventHandler;
diff --git a/openjdkjvmti/alloc_manager.cc b/openjdkjvmti/alloc_manager.cc
new file mode 100644
index 0000000..597ab05
--- /dev/null
+++ b/openjdkjvmti/alloc_manager.cc
@@ -0,0 +1,187 @@
+
+/* Copyright (C) 2019 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "alloc_manager.h"
+
+#include <atomic>
+#include <sstream>
+
+#include "base/logging.h"
+#include "gc/allocation_listener.h"
+#include "gc/heap.h"
+#include "handle.h"
+#include "mirror/class-inl.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "scoped_thread_state_change.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "thread_pool.h"
+
+namespace openjdkjvmti {
+
+template<typename T>
+void AllocationManager::PauseForAllocation(art::Thread* self, T msg) {
+ // The suspension can pause us for arbitrary times. We need to do it to sleep unfortunately. So we
+ // do test, suspend, test again, sleep, repeat.
+ std::string cause;
+ const bool is_logging = VLOG_IS_ON(plugin);
+ while (true) {
+ // We always return when there is no pause and we are runnable.
+ art::Thread* pausing_thread = allocations_paused_thread_.load(std::memory_order_seq_cst);
+ if (LIKELY(pausing_thread == nullptr || pausing_thread == self)) {
+ return;
+ }
+ if (UNLIKELY(is_logging && cause.empty())) {
+ cause = msg();
+ }
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+ art::MutexLock mu(self, alloc_listener_mutex_);
+ pausing_thread = allocations_paused_thread_.load(std::memory_order_seq_cst);
+ CHECK_NE(pausing_thread, self) << "We should always be setting pausing_thread = self!"
+ << " How did this happen? " << *self;
+ if (pausing_thread != nullptr) {
+ VLOG(plugin) << "Suspending " << *self << " due to " << cause << ". Allocation pause "
+ << "initiated by " << *pausing_thread;
+ alloc_pause_cv_.Wait(self);
+ }
+ }
+}
+
+extern AllocationManager* gAllocManager;
+AllocationManager* AllocationManager::Get() {
+ return gAllocManager;
+}
+
+void JvmtiAllocationListener::ObjectAllocated(art::Thread* self,
+ art::ObjPtr<art::mirror::Object>* obj,
+ size_t cnt) {
+ auto cb = manager_->callback_;
+ if (cb != nullptr && manager_->callback_enabled_.load(std::memory_order_seq_cst)) {
+ cb->ObjectAllocated(self, obj, cnt);
+ }
+}
+
+bool JvmtiAllocationListener::HasPreAlloc() const {
+ return manager_->allocations_paused_thread_.load(std::memory_order_seq_cst) != nullptr;
+}
+
+void JvmtiAllocationListener::PreObjectAllocated(art::Thread* self,
+ art::MutableHandle<art::mirror::Class> type,
+ size_t* byte_count) {
+ manager_->PauseForAllocation(self, [&]() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ std::ostringstream oss;
+ oss << "allocating " << *byte_count << " bytes of type " << type->PrettyClass();
+ return oss.str();
+ });
+ if (!type->IsVariableSize()) {
+ *byte_count = type->GetObjectSize();
+ }
+}
+
+AllocationManager::AllocationManager()
+ : alloc_listener_(nullptr),
+ alloc_listener_mutex_("JVMTI Alloc listener",
+ art::LockLevel::kPostUserCodeSuspensionTopLevelLock),
+ alloc_pause_cv_("JVMTI Allocation Pause Condvar", alloc_listener_mutex_) {
+ alloc_listener_.reset(new JvmtiAllocationListener(this));
+}
+
+void AllocationManager::DisableAllocationCallback(art::Thread* self) {
+ callback_enabled_.store(false);
+ DecrListenerInstall(self);
+}
+
+void AllocationManager::EnableAllocationCallback(art::Thread* self) {
+ IncrListenerInstall(self);
+ callback_enabled_.store(true);
+}
+
+void AllocationManager::SetAllocListener(AllocationCallback* callback) {
+ CHECK(callback_ == nullptr) << "Already setup!";
+ callback_ = callback;
+ alloc_listener_.reset(new JvmtiAllocationListener(this));
+}
+
+void AllocationManager::RemoveAllocListener() {
+ callback_enabled_.store(false, std::memory_order_seq_cst);
+ callback_ = nullptr;
+}
+
+void AllocationManager::DecrListenerInstall(art::Thread* self) {
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+ art::MutexLock mu(self, alloc_listener_mutex_);
+ // We don't need any particular memory-order here since we're under the lock, they aren't
+ // changing.
+ if (--listener_refcount_ == 0) {
+ art::Runtime::Current()->GetHeap()->RemoveAllocationListener();
+ }
+}
+
+void AllocationManager::IncrListenerInstall(art::Thread* self) {
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+ art::MutexLock mu(self, alloc_listener_mutex_);
+ // We don't need any particular memory-order here since we're under the lock, they aren't
+ // changing.
+ if (listener_refcount_++ == 0) {
+ art::Runtime::Current()->GetHeap()->SetAllocationListener(alloc_listener_.get());
+ }
+}
+
+void AllocationManager::PauseAllocations(art::Thread* self) {
+ art::Thread* null_thr = nullptr;
+ IncrListenerInstall(self);
+ do {
+ PauseForAllocation(self, []() { return "request to pause allocations on other threads"; });
+ } while (allocations_paused_thread_.compare_exchange_strong(
+ null_thr, self, std::memory_order_seq_cst));
+ // Make sure everything else can see this and isn't in the middle of final allocation.
+ // Force every thread to either be suspended or pass through a barrier.
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+ art::Barrier barrier(0);
+ art::FunctionClosure fc([&](art::Thread* thr ATTRIBUTE_UNUSED) {
+ barrier.Pass(art::Thread::Current());
+ });
+ size_t requested = art::Runtime::Current()->GetThreadList()->RunCheckpoint(&fc);
+ barrier.Increment(self, requested);
+}
+
+void AllocationManager::ResumeAllocations(art::Thread* self) {
+ CHECK_EQ(allocations_paused_thread_.load(), self) << "not paused! ";
+ DecrListenerInstall(self);
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+ art::MutexLock mu(self, alloc_listener_mutex_);
+ allocations_paused_thread_.store(nullptr, std::memory_order_seq_cst);
+ alloc_pause_cv_.Broadcast(self);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/alloc_manager.h b/openjdkjvmti/alloc_manager.h
new file mode 100644
index 0000000..c89d9a6
--- /dev/null
+++ b/openjdkjvmti/alloc_manager.h
@@ -0,0 +1,114 @@
+/* Copyright (C) 2019 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_ALLOC_MANAGER_H_
+#define ART_OPENJDKJVMTI_ALLOC_MANAGER_H_
+
+#include <jvmti.h>
+
+#include <atomic>
+
+#include "base/locks.h"
+#include "base/mutex.h"
+#include "gc/allocation_listener.h"
+
+namespace art {
+template <typename T> class MutableHandle;
+template <typename T> class ObjPtr;
+class Thread;
+namespace mirror {
+class Class;
+class Object;
+} // namespace mirror
+} // namespace art
+
+namespace openjdkjvmti {
+
+class AllocationManager;
+
+class JvmtiAllocationListener : public art::gc::AllocationListener {
+ public:
+ explicit JvmtiAllocationListener(AllocationManager* manager) : manager_(manager) {}
+ void ObjectAllocated(art::Thread* self,
+ art::ObjPtr<art::mirror::Object>* obj,
+ size_t cnt) override REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool HasPreAlloc() const override REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void PreObjectAllocated(art::Thread* self,
+ art::MutableHandle<art::mirror::Class> type,
+ size_t* byte_count) override REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+ AllocationManager* manager_;
+};
+
+class AllocationManager {
+ public:
+ class AllocationCallback {
+ public:
+ virtual ~AllocationCallback() {}
+ virtual void ObjectAllocated(art::Thread* self,
+ art::ObjPtr<art::mirror::Object>* obj,
+ size_t byte_count) REQUIRES_SHARED(art::Locks::mutator_lock_) = 0;
+ };
+
+ AllocationManager();
+
+ void SetAllocListener(AllocationCallback* callback);
+ void RemoveAllocListener();
+
+ static AllocationManager* Get();
+
+ void PauseAllocations(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void ResumeAllocations(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ void EnableAllocationCallback(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void DisableAllocationCallback(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+ template<typename T>
+ void PauseForAllocation(art::Thread* self, T msg) REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void IncrListenerInstall(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void DecrListenerInstall(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ AllocationCallback* callback_ = nullptr;
+ uint32_t listener_refcount_ GUARDED_BY(alloc_listener_mutex_) = 0;
+ std::atomic<art::Thread*> allocations_paused_thread_ = nullptr;
+ std::atomic<bool> callback_enabled_ = false;
+ std::unique_ptr<JvmtiAllocationListener> alloc_listener_ = nullptr;
+ art::Mutex alloc_listener_mutex_ ACQUIRED_AFTER(art::Locks::user_code_suspension_lock_);
+ art::ConditionVariable alloc_pause_cv_;
+
+ friend class JvmtiAllocationListener;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_ALLOC_MANAGER_H_
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 56406fc..64a02e8 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -31,6 +31,7 @@
#include <android-base/thread_annotations.h>
+#include "alloc_manager.h"
#include "base/locks.h"
#include "base/mutex.h"
#include "events-inl.h"
@@ -312,9 +313,9 @@
DISALLOW_COPY_AND_ASSIGN(JvmtiDdmChunkListener);
};
-class JvmtiAllocationListener : public art::gc::AllocationListener {
+class JvmtiEventAllocationListener : public AllocationManager::AllocationCallback {
public:
- explicit JvmtiAllocationListener(EventHandler* handler) : handler_(handler) {}
+ explicit JvmtiEventAllocationListener(EventHandler* handler) : handler_(handler) {}
void ObjectAllocated(art::Thread* self, art::ObjPtr<art::mirror::Object>* obj, size_t byte_count)
override REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -349,15 +350,14 @@
EventHandler* handler_;
};
-static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, bool enable) {
+static void SetupObjectAllocationTracking(bool enable) {
// We must not hold the mutator lock here, but if we're in FastJNI, for example, we might. For
// now, do a workaround: (possibly) acquire and release.
art::ScopedObjectAccess soa(art::Thread::Current());
- art::ScopedThreadSuspension sts(soa.Self(), art::ThreadState::kSuspended);
if (enable) {
- art::Runtime::Current()->GetHeap()->SetAllocationListener(listener);
+ AllocationManager::Get()->EnableAllocationCallback(soa.Self());
} else {
- art::Runtime::Current()->GetHeap()->RemoveAllocationListener();
+ AllocationManager::Get()->DisableAllocationCallback(soa.Self());
}
}
@@ -1327,7 +1327,7 @@
SetupDdmTracking(ddm_listener_.get(), enable);
return;
case ArtJvmtiEvent::kVmObjectAlloc:
- SetupObjectAllocationTracking(alloc_listener_.get(), enable);
+ SetupObjectAllocationTracking(enable);
return;
case ArtJvmtiEvent::kGarbageCollectionStart:
case ArtJvmtiEvent::kGarbageCollectionFinish:
@@ -1665,13 +1665,15 @@
art::ScopedSuspendAll ssa("jvmti method tracing uninstallation");
// Just remove every possible event.
art::Runtime::Current()->GetInstrumentation()->RemoveListener(method_trace_listener_.get(), ~0);
+ AllocationManager::Get()->RemoveAllocListener();
}
EventHandler::EventHandler()
: envs_lock_("JVMTI Environment List Lock", art::LockLevel::kPostMutatorTopLockLevel),
frame_pop_enabled(false),
internal_event_refcount_({0}) {
- alloc_listener_.reset(new JvmtiAllocationListener(this));
+ alloc_listener_.reset(new JvmtiEventAllocationListener(this));
+ AllocationManager::Get()->SetAllocListener(alloc_listener_.get());
ddm_listener_.reset(new JvmtiDdmChunkListener(this));
gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index c9d587a..d4eb171 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -34,7 +34,7 @@
namespace openjdkjvmti {
struct ArtJvmTiEnv;
-class JvmtiAllocationListener;
+class JvmtiEventAllocationListener;
class JvmtiDdmChunkListener;
class JvmtiGcPauseListener;
class JvmtiMethodTraceListener;
@@ -425,7 +425,7 @@
// A union of all enabled events, anywhere.
EventMask global_mask;
- std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
+ std::unique_ptr<JvmtiEventAllocationListener> alloc_listener_;
std::unique_ptr<JvmtiDdmChunkListener> ddm_listener_;
std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
index 1d18390..b25b4d1 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -17,6 +17,7 @@
#include "ti_heap.h"
#include <ios>
+#include <unordered_map>
#include "android-base/logging.h"
#include "android-base/thread_annotations.h"
@@ -1613,8 +1614,9 @@
namespace {
using ObjectPtr = art::ObjPtr<art::mirror::Object>;
+using ObjectMap = std::unordered_map<ObjectPtr, ObjectPtr, art::HashObjPtr>;
-static void ReplaceObjectReferences(ObjectPtr old_obj_ptr, ObjectPtr new_obj_ptr)
+static void ReplaceObjectReferences(const ObjectMap& map)
REQUIRES(art::Locks::mutator_lock_,
art::Roles::uninterruptible_) {
art::Runtime::Current()->GetHeap()->VisitObjectsPaused(
@@ -1623,8 +1625,7 @@
class ResizeReferenceVisitor {
public:
using CompressedObj = art::mirror::CompressedReference<art::mirror::Object>;
- ResizeReferenceVisitor(ObjectPtr old_arr, ObjectPtr new_arr)
- : old_obj_(old_arr), new_obj_(new_arr) {}
+ explicit ResizeReferenceVisitor(const ObjectMap& map) : map_(map) {}
// Ignore class roots.
void VisitRootIfNonNull(CompressedObj* root) const
@@ -1634,20 +1635,29 @@
}
}
void VisitRoot(CompressedObj* root) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
- if (root->AsMirrorPtr() == old_obj_) {
- root->Assign(new_obj_);
- art::WriteBarrier::ForEveryFieldWrite(new_obj_);
+ auto it = map_.find(root->AsMirrorPtr());
+ if (it != map_.end()) {
+ root->Assign(it->second);
+ art::WriteBarrier::ForEveryFieldWrite(it->second);
}
}
void operator()(art::ObjPtr<art::mirror::Object> obj,
art::MemberOffset off,
- bool is_static ATTRIBUTE_UNUSED) const
+ bool is_static) const
REQUIRES_SHARED(art::Locks::mutator_lock_) {
- if (obj->GetFieldObject<art::mirror::Object>(off) == old_obj_) {
+ auto it = map_.find(obj->GetFieldObject<art::mirror::Object>(off));
+ if (it != map_.end()) {
+ UNUSED(is_static);
+ if (UNLIKELY(!is_static && off == art::mirror::Object::ClassOffset())) {
+ // 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;
+ }
VLOG(plugin) << "Updating field at offset " << off.Uint32Value() << " of type "
<< obj->GetClass()->PrettyClass();
- obj->SetFieldObject</*transaction*/ false>(off, new_obj_);
+ obj->SetFieldObject</*transaction*/ false>(off, it->second);
+ art::WriteBarrier::ForEveryFieldWrite(obj);
}
}
@@ -1659,11 +1669,10 @@
}
private:
- ObjectPtr old_obj_;
- ObjectPtr new_obj_;
+ const ObjectMap& map_;
};
- ResizeReferenceVisitor rrv(old_obj_ptr, new_obj_ptr);
+ ResizeReferenceVisitor rrv(map);
if (ref->IsClass()) {
// Class object native roots are the ArtField and ArtMethod 'declaring_class_' fields
// which we don't want to be messing with as it would break ref-visitor assumptions about
@@ -1678,13 +1687,12 @@
});
}
-static void ReplaceStrongRoots(art::Thread* self, ObjectPtr old_obj_ptr, ObjectPtr new_obj_ptr)
+static void ReplaceStrongRoots(art::Thread* self, const ObjectMap& map)
REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
// replace root references expcept java frames.
struct ResizeRootVisitor : public art::RootVisitor {
public:
- ResizeRootVisitor(ObjectPtr new_val, ObjectPtr old_val)
- : new_val_(new_val), old_val_(old_val) {}
+ explicit ResizeRootVisitor(const ObjectMap& map) : map_(map) {}
// TODO It's somewhat annoying to have to have this function implemented twice. It might be
// good/useful to implement operator= for CompressedReference to allow us to use a template to
@@ -1693,7 +1701,8 @@
REQUIRES_SHARED(art::Locks::mutator_lock_) {
art::mirror::Object*** end = roots + count;
for (art::mirror::Object** obj = *roots; roots != end; obj = *(++roots)) {
- if (*obj == old_val_) {
+ auto it = map_.find(*obj);
+ if (it != map_.end()) {
// Java frames might have the JIT doing optimizations (for example loop-unrolling or
// eliding bounds checks) so we need deopt them once we're done here.
if (info.GetType() == art::RootType::kRootJavaFrame) {
@@ -1708,7 +1717,7 @@
threads_with_roots_.insert(info.GetThreadId());
}
}
- *obj = new_val_.Ptr();
+ *obj = it->second.Ptr();
}
}
}
@@ -1719,7 +1728,8 @@
art::mirror::CompressedReference<art::mirror::Object>** end = roots + count;
for (art::mirror::CompressedReference<art::mirror::Object>* obj = *roots; roots != end;
obj = *(++roots)) {
- if (obj->AsMirrorPtr() == old_val_) {
+ auto it = map_.find(obj->AsMirrorPtr());
+ if (it != map_.end()) {
// Java frames might have the JIT doing optimizations (for example loop-unrolling or
// eliding bounds checks) so we need deopt them once we're done here.
if (info.GetType() == art::RootType::kRootJavaFrame) {
@@ -1734,7 +1744,7 @@
threads_with_roots_.insert(info.GetThreadId());
}
}
- obj->Assign(new_val_);
+ obj->Assign(it->second);
}
}
}
@@ -1744,11 +1754,10 @@
}
private:
- ObjectPtr new_val_;
- ObjectPtr old_val_;
+ const ObjectMap& map_;
std::unordered_set<uint32_t> threads_with_roots_;
};
- ResizeRootVisitor rrv(new_obj_ptr, old_obj_ptr);
+ ResizeRootVisitor rrv(map);
art::Runtime::Current()->VisitRoots(&rrv, art::VisitRootFlags::kVisitRootFlagAllRoots);
// Handle java Frames. Annoyingly the JIT can embed information about the length of the array into
// the compiled code. By changing the length of the array we potentially invalidate these
@@ -1773,8 +1782,7 @@
static void ReplaceWeakRoots(art::Thread* self,
EventHandler* event_handler,
- ObjectPtr old_obj_ptr,
- ObjectPtr new_obj_ptr)
+ const ObjectMap& map)
REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
// Handle tags. We want to do this seprately from other weak-refs (handled below) because we need
// to send additional events and handle cases where the agent might have tagged the new
@@ -1786,25 +1794,33 @@
// situations where the order of weak-ref visiting affects the final tagging state. Since we have
// the mutator_lock_ and gc-paused throughout this whole process no threads should be able to see
// the interval where the objects are not tagged.
- std::unordered_map<ArtJvmTiEnv*, jlong> obsolete_tags;
- std::unordered_map<ArtJvmTiEnv*, jlong> non_obsolete_tags;
+ struct NewTagValue {
+ public:
+ ObjectPtr obsolete_obj_;
+ jlong obsolete_tag_;
+ ObjectPtr new_obj_;
+ jlong new_tag_;
+ };
+
+ // Map from the environment to the list of <obsolete_tag, new_tag> pairs that were changed.
+ std::unordered_map<ArtJvmTiEnv*, std::vector<NewTagValue>> changed_tags;
event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) {
// Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it.
art::Locks::mutator_lock_->AssertExclusiveHeld(self);
env->object_tag_table->Lock();
// Get the tags and clear them (so we don't need to special-case the normal weak-ref visitor)
- jlong new_tag = 0;
- jlong obsolete_tag = 0;
- bool had_new_tag = env->object_tag_table->RemoveLocked(new_obj_ptr, &new_tag);
- bool had_obsolete_tag = env->object_tag_table->RemoveLocked(old_obj_ptr, &obsolete_tag);
- // Dispatch event.
- if (had_obsolete_tag || had_new_tag) {
- event_handler->DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>(env,
- self,
- &obsolete_tag,
- &new_tag);
- obsolete_tags[env] = obsolete_tag;
- non_obsolete_tags[env] = new_tag;
+ for (auto it : map) {
+ jlong new_tag = 0;
+ jlong obsolete_tag = 0;
+ bool had_obsolete_tag = env->object_tag_table->RemoveLocked(it.first, &obsolete_tag);
+ bool had_new_tag = env->object_tag_table->RemoveLocked(it.second, &new_tag);
+ // Dispatch event.
+ if (had_obsolete_tag || had_new_tag) {
+ event_handler->DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>(
+ env, self, &obsolete_tag, &new_tag);
+ changed_tags.try_emplace(env).first->second.push_back(
+ { it.first, obsolete_tag, it.second, new_tag });
+ }
}
// After weak-ref update we need to go back and re-add obsoletes. We wait to avoid having to
// deal with the visit-weaks overwriting the initial new_obj_ptr tag and generally making things
@@ -1814,34 +1830,34 @@
// Handle weak-refs.
struct ReplaceWeaksVisitor : public art::IsMarkedVisitor {
public:
- ReplaceWeaksVisitor(ObjectPtr old_obj, ObjectPtr new_obj)
- : old_obj_(old_obj), new_obj_(new_obj) {}
+ ReplaceWeaksVisitor(const ObjectMap& map) : map_(map) {}
art::mirror::Object* IsMarked(art::mirror::Object* obj)
REQUIRES_SHARED(art::Locks::mutator_lock_) {
- if (obj == old_obj_) {
- return new_obj_.Ptr();
+ auto it = map_.find(obj);
+ if (it != map_.end()) {
+ return it->second.Ptr();
} else {
return obj;
}
}
private:
- ObjectPtr old_obj_;
- ObjectPtr new_obj_;
+ const ObjectMap& map_;
};
- ReplaceWeaksVisitor rwv(old_obj_ptr, new_obj_ptr);
+ ReplaceWeaksVisitor rwv(map);
art::Runtime::Current()->SweepSystemWeaks(&rwv);
// Re-add the object tags. At this point all weak-references to the old_obj_ptr are gone.
event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) {
// Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it.
art::Locks::mutator_lock_->AssertExclusiveHeld(self);
env->object_tag_table->Lock();
- if (obsolete_tags.find(env) != obsolete_tags.end()) {
- env->object_tag_table->SetLocked(old_obj_ptr, obsolete_tags[env]);
- }
- if (non_obsolete_tags.find(env) != non_obsolete_tags.end()) {
- env->object_tag_table->SetLocked(new_obj_ptr, non_obsolete_tags[env]);
+ auto it = changed_tags.find(env);
+ if (it != changed_tags.end()) {
+ for (const NewTagValue& v : it->second) {
+ env->object_tag_table->SetLocked(v.obsolete_obj_, v.obsolete_tag_);
+ env->object_tag_table->SetLocked(v.new_obj_, v.new_tag_);
+ }
}
env->object_tag_table->Unlock();
});
@@ -1852,9 +1868,14 @@
void HeapExtensions::ReplaceReference(art::Thread* self,
art::ObjPtr<art::mirror::Object> old_obj_ptr,
art::ObjPtr<art::mirror::Object> new_obj_ptr) {
- ReplaceObjectReferences(old_obj_ptr, new_obj_ptr);
- ReplaceStrongRoots(self, old_obj_ptr, new_obj_ptr);
- ReplaceWeakRoots(self, HeapExtensions::gEventHandler, old_obj_ptr, new_obj_ptr);
+ ObjectMap map { { old_obj_ptr, new_obj_ptr } };
+ ReplaceReferences(self, map);
+}
+
+void HeapExtensions::ReplaceReferences(art::Thread* self, const ObjectMap& map) {
+ ReplaceObjectReferences(map);
+ ReplaceStrongRoots(self, map);
+ ReplaceWeakRoots(self, HeapExtensions::gEventHandler, map);
}
jvmtiError HeapExtensions::ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size) {
diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
index 2e27cc7..ee8b4d6 100644
--- a/openjdkjvmti/ti_heap.h
+++ b/openjdkjvmti/ti_heap.h
@@ -17,6 +17,8 @@
#ifndef ART_OPENJDKJVMTI_TI_HEAP_H_
#define ART_OPENJDKJVMTI_TI_HEAP_H_
+#include <unordered_map>
+
#include "jvmti.h"
#include "base/locks.h"
@@ -24,6 +26,7 @@
namespace art {
class Thread;
template<typename T> class ObjPtr;
+class HashObjPtr;
namespace mirror {
class Object;
} // namespace mirror
@@ -88,6 +91,13 @@
static jvmtiError JNICALL ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size);
+ static void ReplaceReferences(
+ art::Thread* self,
+ const std::unordered_map<art::ObjPtr<art::mirror::Object>,
+ art::ObjPtr<art::mirror::Object>,
+ art::HashObjPtr>& refs)
+ REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_);
+
static void ReplaceReference(art::Thread* self,
art::ObjPtr<art::mirror::Object> original,
art::ObjPtr<art::mirror::Object> replacement)
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 87080ca..ebbe6ac 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -42,6 +42,8 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include "alloc_manager.h"
+#include "android-base/macros.h"
#include "android-base/thread_annotations.h"
#include "art_field-inl.h"
#include "art_field.h"
@@ -115,6 +117,7 @@
#include "reflective_value_visitor.h"
#include "runtime.h"
#include "runtime_globals.h"
+#include "scoped_thread_state_change.h"
#include "stack.h"
#include "thread.h"
#include "thread_list.h"
@@ -460,30 +463,33 @@
}
// Check for already existing non-static fields/methods.
// TODO Remove this once we support generic method/field addition.
- 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());
+ 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);
}
- }, 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());
+ 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);
}
- });
- 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
@@ -985,9 +991,12 @@
return old_method_id == new_method_id;
});
+ if (!new_method.IsStaticOrDirect()) {
+ RecordHasVirtualMembers();
+ }
if (old_iter == old_methods.cend()) {
// TODO Support adding non-static methods.
- if (is_structural && new_method.IsStaticOrDirect()) {
+ if (is_structural && (new_method.IsStaticOrDirect() || h_klass->IsFinal())) {
RecordNewMethodAdded();
} else {
RecordFailure(
@@ -1046,9 +1055,12 @@
FieldNameAndSignature old_field_id(&old_dex_file, old_iter.GetIndex());
return old_field_id == new_field_id;
});
+ if (!new_field.IsStatic()) {
+ RecordHasVirtualMembers();
+ }
if (old_iter == old_fields.cend()) {
// TODO Support adding non-static fields.
- if (driver_->IsStructuralRedefinition() && new_field.IsStatic()) {
+ if (driver_->IsStructuralRedefinition() && (new_field.IsStatic() || h_klass->IsFinal())) {
RecordNewFieldAdded();
} else {
RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
@@ -1169,6 +1181,10 @@
jvmtiError res;
if (driver_->type_ == RedefinitionType::kStructural && this->IsStructuralRedefinition()) {
res = Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(h_klass, &err);
+ if (res == OK && HasVirtualMembers() && h_klass->IsFinalizable()) {
+ res = ERR(INTERNAL);
+ err = "Cannot redefine finalizable objects at this time.";
+ }
} else {
res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err);
}
@@ -1201,9 +1217,11 @@
kSlotOldObsoleteMethods = 6,
kSlotOldDexCaches = 7,
kSlotNewClassObject = 8,
+ kSlotOldInstanceObjects = 9,
+ kSlotNewInstanceObjects = 10,
// Must be last one.
- kNumSlots = 9,
+ kNumSlots = 11,
};
// This needs to have a HandleScope passed in that is capable of creating a new Handle without
@@ -1269,6 +1287,18 @@
return art::ObjPtr<art::mirror::Class>::DownCast(GetSlot(klass_index, kSlotNewClassObject));
}
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetOldInstanceObjects(
+ jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>>::DownCast(
+ GetSlot(klass_index, kSlotOldInstanceObjects));
+ }
+
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetNewInstanceObjects(
+ jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>>::DownCast(
+ GetSlot(klass_index, kSlotNewInstanceObjects));
+ }
+
void SetSourceClassLoader(jint klass_index, art::ObjPtr<art::mirror::ClassLoader> loader)
REQUIRES_SHARED(art::Locks::mutator_lock_) {
SetSlot(klass_index, kSlotSourceClassLoader, loader);
@@ -1308,6 +1338,16 @@
SetSlot(klass_index, kSlotNewClassObject, klass);
}
+ void SetOldInstanceObjects(jint klass_index,
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotOldInstanceObjects, objs);
+ }
+ void SetNewInstanceObjects(jint klass_index,
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotNewInstanceObjects, objs);
+ }
int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
return arr_->GetLength() / kNumSlots;
}
@@ -1392,6 +1432,11 @@
return *this;
}
+ // Compat for STL iterators.
+ RedefinitionDataIter& operator*() {
+ return *this;
+ }
+
Redefiner::ClassRedefinition& GetRedefinition() REQUIRES_SHARED(art::Locks::mutator_lock_) {
return (*holder_.GetRedefinitions())[idx_];
}
@@ -1438,6 +1483,14 @@
return holder_.GetNewClassObject(idx_);
}
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetOldInstanceObjects() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetOldInstanceObjects(idx_);
+ }
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetNewInstanceObjects() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetNewInstanceObjects(idx_);
+ }
int32_t GetIndex() const {
return idx_;
}
@@ -1478,6 +1531,14 @@
REQUIRES_SHARED(art::Locks::mutator_lock_) {
holder_.SetNewClassObject(idx_, klass);
}
+ void SetOldInstanceObjects(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetOldInstanceObjects(idx_, objs);
+ }
+ void SetNewInstanceObjects(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetNewInstanceObjects(idx_, objs);
+ }
private:
int32_t idx_;
@@ -1580,6 +1641,75 @@
return true;
}
+bool Redefiner::ClassRedefinition::CollectAndCreateNewInstances(
+ /*out*/ RedefinitionDataIter* cur_data) {
+ if (!IsStructuralRedefinition()) {
+ return true;
+ }
+ 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;
+ art::gc::Heap* heap = driver_->runtime_->GetHeap();
+ 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();
+ }
+ };
+ 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));
+ }
+ });
+ VLOG(plugin) << "Collected " << old_instances.size() << " instances to recreate!";
+
+ art::Handle<art::mirror::Class> obj_array_class(
+ hs.NewHandle(art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Object>>(
+ driver_->runtime_->GetClassLinker())));
+ art::Handle<art::mirror::ObjectArray<art::mirror::Object>> old_instances_arr(
+ hs.NewHandle(art::mirror::ObjectArray<art::mirror::Object>::Alloc(
+ driver_->self_, obj_array_class.Get(), old_instances.size())));
+ if (old_instances_arr.IsNull()) {
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate old_instance arrays!");
+ return false;
+ }
+ for (uint32_t i = 0; i < old_instances.size(); ++i) {
+ old_instances_arr->Set(i, old_instances[i].Get());
+ }
+ cur_data->SetOldInstanceObjects(old_instances_arr.Get());
+
+ art::Handle<art::mirror::ObjectArray<art::mirror::Object>> new_instances_arr(
+ hs.NewHandle(art::mirror::ObjectArray<art::mirror::Object>::Alloc(
+ driver_->self_, obj_array_class.Get(), old_instances.size())));
+ if (new_instances_arr.IsNull()) {
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ 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_));
+ if (new_instance.IsNull()) {
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ std::string msg(
+ StringPrintf("Could not allocate instance %d of %zu", i, old_instances.size()));
+ RecordFailure(ERR(OUT_OF_MEMORY), msg);
+ return false;
+ }
+ new_instances_arr->Set(i, new_instance);
+ }
+ cur_data->SetNewInstanceObjects(new_instances_arr.Get());
+ return true;
+}
+
bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
/*out*/RedefinitionDataIter* cur_data) {
art::ScopedObjectAccessUnchecked soa(driver_->self_);
@@ -1801,6 +1931,16 @@
return true;
}
+bool Redefiner::CollectAndCreateNewInstances(RedefinitionDataHolder& holder) {
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ // Allocate the data this redefinition requires.
+ if (!data.GetRedefinition().CollectAndCreateNewInstances(&data)) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) {
for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
// Allocate the data this redefinition requires.
@@ -1849,6 +1989,36 @@
art::Thread* self_;
};
+class ScopedSuspendAllocations {
+ public:
+ ScopedSuspendAllocations(art::Runtime* runtime, RedefinitionDataHolder& h)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ : paused_(false) {
+ if (std::any_of(h.begin(),
+ h.end(),
+ [](auto r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return r.GetRedefinition().IsStructuralRedefinition();
+ })) {
+ VLOG(plugin) << "Pausing allocations for structural redefinition.";
+ paused_ = true;
+ AllocationManager::Get()->PauseAllocations(art::Thread::Current());
+ // Collect garbage so we don't need to recreate as much.
+ runtime->GetHeap()->CollectGarbage(/*clear_soft_references=*/false);
+ }
+ }
+
+ ~ScopedSuspendAllocations() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (paused_) {
+ AllocationManager::Get()->ResumeAllocations(art::Thread::Current());
+ }
+ }
+
+ private:
+ bool paused_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSuspendAllocations);
+};
+
jvmtiError Redefiner::Run() {
art::StackHandleScope<1> hs(self_);
// Allocate an array to hold onto all java temporary objects associated with this redefinition.
@@ -1873,6 +2043,11 @@
return result_;
}
+ ScopedSuspendAllocations suspend_alloc(runtime_, holder);
+ if (!CollectAndCreateNewInstances(holder)) {
+ return result_;
+ }
+
// At this point we can no longer fail without corrupting the runtime state.
for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
art::ClassLinker* cl = runtime_->GetClassLinker();
@@ -2023,6 +2198,120 @@
}
}
+static void CopyField(art::ObjPtr<art::mirror::Object> target,
+ art::ArtField* new_field,
+ art::ObjPtr<art::mirror::Object> source,
+ art::ArtField& old_field) REQUIRES(art::Locks::mutator_lock_) {
+ art::Primitive::Type ftype = old_field.GetTypeAsPrimitiveType();
+ CHECK_EQ(ftype, new_field->GetTypeAsPrimitiveType())
+ << old_field.PrettyField() << " vs " << new_field->PrettyField();
+ if (ftype == art::Primitive::kPrimNot) {
+ new_field->SetObject<false>(target, old_field.GetObject(source));
+ } else {
+ switch (ftype) {
+#define UPDATE_FIELD(TYPE) \
+ case art::Primitive::kPrim##TYPE: \
+ new_field->Set##TYPE<false>(target, old_field.Get##TYPE(source)); \
+ break
+ UPDATE_FIELD(Int);
+ UPDATE_FIELD(Float);
+ UPDATE_FIELD(Long);
+ UPDATE_FIELD(Double);
+ UPDATE_FIELD(Short);
+ UPDATE_FIELD(Char);
+ UPDATE_FIELD(Byte);
+ UPDATE_FIELD(Boolean);
+ case art::Primitive::kPrimNot:
+ case art::Primitive::kPrimVoid:
+ LOG(FATAL) << "Unexpected field with type " << ftype << " found!";
+ UNREACHABLE();
+#undef UPDATE_FIELD
+ }
+ }
+}
+
+static void CopyFields(bool is_static,
+ art::ObjPtr<art::mirror::Object> target,
+ art::ObjPtr<art::mirror::Class> target_class,
+ art::ObjPtr<art::mirror::Object> source,
+ art::ObjPtr<art::mirror::Class> source_class)
+ REQUIRES(art::Locks::mutator_lock_) {
+ DCHECK(!source_class->IsObjectClass() && !target_class->IsObjectClass())
+ << "Should not be overriding object class fields. Target: " << target_class->PrettyClass()
+ << " Source: " << source_class->PrettyClass();
+ for (art::ArtField& f : (is_static ? source_class->GetSFields() : source_class->GetIFields())) {
+ art::ArtField* new_field =
+ (is_static ? target_class->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor())
+ : target_class->FindDeclaredInstanceField(f.GetName(), f.GetTypeDescriptor()));
+ CHECK(new_field != nullptr) << "could not find new version of " << f.PrettyField();
+ CopyField(target, new_field, source, f);
+ }
+ if (!is_static && !target_class->GetSuperClass()->IsObjectClass()) {
+ CopyFields(
+ is_static, target, target_class->GetSuperClass(), source, source_class->GetSuperClass());
+ }
+}
+
+static void ClearField(art::ObjPtr<art::mirror::Object> target, art::ArtField& field)
+ REQUIRES(art::Locks::mutator_lock_) {
+ art::Primitive::Type ftype = field.GetTypeAsPrimitiveType();
+ if (ftype == art::Primitive::kPrimNot) {
+ field.SetObject<false>(target, nullptr);
+ } else {
+ switch (ftype) {
+#define UPDATE_FIELD(TYPE) \
+ case art::Primitive::kPrim##TYPE: \
+ field.Set##TYPE<false>(target, 0); \
+ break
+ UPDATE_FIELD(Int);
+ UPDATE_FIELD(Float);
+ UPDATE_FIELD(Long);
+ UPDATE_FIELD(Double);
+ UPDATE_FIELD(Short);
+ UPDATE_FIELD(Char);
+ UPDATE_FIELD(Byte);
+ UPDATE_FIELD(Boolean);
+ case art::Primitive::kPrimNot:
+ case art::Primitive::kPrimVoid:
+ LOG(FATAL) << "Unexpected field with type " << ftype << " found!";
+ UNREACHABLE();
+#undef UPDATE_FIELD
+ }
+ }
+}
+
+static void ClearFields(bool is_static,
+ art::ObjPtr<art::mirror::Object> target,
+ art::ObjPtr<art::mirror::Class> target_class)
+ REQUIRES(art::Locks::mutator_lock_) {
+ DCHECK(!target_class->IsObjectClass());
+ for (art::ArtField& f : (is_static ? target_class->GetSFields() : target_class->GetIFields())) {
+ ClearField(target, f);
+ }
+ if (!is_static && !target_class->GetSuperClass()->IsObjectClass()) {
+ ClearFields(is_static, target, target_class->GetSuperClass());
+ }
+}
+
+static void CopyAndClearFields(bool is_static,
+ art::ObjPtr<art::mirror::Object> target,
+ art::ObjPtr<art::mirror::Class> target_class,
+ art::ObjPtr<art::mirror::Object> source,
+ art::ObjPtr<art::mirror::Class> source_class)
+ REQUIRES(art::Locks::mutator_lock_) {
+ // Copy all non-j.l.Object fields
+ CopyFields(is_static, target, target_class, source, source_class);
+ // Copy the lock-word.
+ target->SetLockWord(source->GetLockWord(false), false);
+ // Clear (reset) the old one.
+ source->SetLockWord(art::LockWord::Default(), false);
+ art::WriteBarrier::ForEveryFieldWrite(target);
+
+ // Clear the fields from the old class. We don't need it anymore.
+ ClearFields(is_static, source, source_class);
+ art::WriteBarrier::ForEveryFieldWrite(source);
+}
+
void Redefiner::ClassRedefinition::UpdateClassStructurally(const RedefinitionDataIter& holder) {
DCHECK(IsStructuralRedefinition());
// LETS GO. We've got all new class structures so no need to do all the updating of the stacks.
@@ -2036,40 +2325,24 @@
std::map<art::ArtMethod*, art::ArtMethod*> method_map;
std::map<art::ArtField*, art::ArtField*> field_map;
CollectNewFieldAndMethodMappings(holder, &method_map, &field_map);
- // Copy over the fields of the object.
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> new_instances(
+ holder.GetNewInstanceObjects());
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> old_instances(
+ holder.GetOldInstanceObjects());
CHECK(!orig.IsNull());
CHECK(!replacement.IsNull());
- for (art::ArtField& f : orig->GetSFields()) {
- art::ArtField* new_field =
- replacement->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
- CHECK(new_field != nullptr) << "could not find new version of " << f.PrettyField();
- art::Primitive::Type ftype = f.GetTypeAsPrimitiveType();
- CHECK_EQ(ftype, new_field->GetTypeAsPrimitiveType())
- << f.PrettyField() << " vs " << new_field->PrettyField();
- if (ftype == art::Primitive::kPrimNot) {
- new_field->SetObject<false>(replacement, f.GetObject(orig));
- } else {
- switch (ftype) {
-#define UPDATE_FIELD(TYPE) \
- case art::Primitive::kPrim##TYPE: \
- new_field->Set##TYPE<false>(replacement, f.Get##TYPE(orig)); \
- break
+ // Copy over the static fields of the class and all the instance fields.
+ CopyAndClearFields(/*is_static=*/true, replacement, replacement, orig, orig);
- UPDATE_FIELD(Int);
- UPDATE_FIELD(Float);
- UPDATE_FIELD(Long);
- UPDATE_FIELD(Double);
- UPDATE_FIELD(Short);
- UPDATE_FIELD(Char);
- UPDATE_FIELD(Byte);
- UPDATE_FIELD(Boolean);
- case art::Primitive::kPrimNot:
- case art::Primitive::kPrimVoid:
- LOG(FATAL) << "Unexpected field with type " << ftype << " found!";
- UNREACHABLE();
-#undef UPDATE_FIELD
- }
- }
+ // 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));
+ CopyAndClearFields(/*is_static=*/false,
+ new_instance,
+ new_instance->GetClass(),
+ old_instance,
+ old_instance->GetClass());
}
// Mark old class obsolete.
orig->SetObsoleteObject();
@@ -2079,9 +2352,6 @@
m.SetDontCompile();
DCHECK_EQ(orig, m.GetDeclaringClass());
}
- // Copy the lock-word
- replacement->SetLockWord(orig->GetLockWord(false), false);
- orig->SetLockWord(art::LockWord::Default(), false);
// Update live pointers in ART code.
auto could_change_resolution_of = [&](auto* field_or_method,
const auto& info) REQUIRES(art::Locks::mutator_lock_) {
@@ -2166,39 +2436,23 @@
// Force every frame of every thread to deoptimize (any frame might have eg offsets compiled in).
driver_->runtime_->GetInstrumentation()->DeoptimizeAllThreadFrames();
- // Actually perform the general replacement. This doesn't affect ArtMethod/ArtFields.
- // This replaces the mirror::Class in 'holder' as well. It's magic!
- HeapExtensions::ReplaceReference(driver_->self_, orig, replacement);
+ 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));
+ }
+
+ // Actually perform the general replacement. This doesn't affect ArtMethod/ArtFields. It does
+ // affect the declaring_class field of all the obsolete objects, which is unfortunate and needs to
+ // be undone. This replaces the mirror::Class in 'holder' as well. It's magic!
+ HeapExtensions::ReplaceReferences(driver_->self_, map);
// 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);
- // Clear the static fields of the old-class.
- for (art::ArtField& f : orig->GetSFields()) {
- switch (f.GetTypeAsPrimitiveType()) {
- #define UPDATE_FIELD(TYPE) \
- case art::Primitive::kPrim ## TYPE: \
- f.Set ## TYPE <false>(orig, 0); \
- break
-
- UPDATE_FIELD(Int);
- UPDATE_FIELD(Float);
- UPDATE_FIELD(Long);
- UPDATE_FIELD(Double);
- UPDATE_FIELD(Short);
- UPDATE_FIELD(Char);
- UPDATE_FIELD(Byte);
- UPDATE_FIELD(Boolean);
- case art::Primitive::kPrimNot:
- f.SetObject<false>(orig, nullptr);
- break;
- case art::Primitive::kPrimVoid:
- LOG(FATAL) << "Unexpected field with type void found!";
- UNREACHABLE();
- #undef UPDATE_FIELD
- }
- }
art::jit::Jit* jit = driver_->runtime_->GetJit();
if (jit != nullptr) {
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 58a688c..cedce92 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -41,6 +41,7 @@
#include "art_jvmti.h"
#include "base/array_ref.h"
#include "base/globals.h"
+#include "dex/class_accessor.h"
#include "dex/dex_file.h"
#include "dex/dex_file_structs.h"
#include "jni/jni_env_ext-inl.h"
@@ -155,6 +156,9 @@
bool FinishRemainingAllocations(/*out*/RedefinitionDataIter* cur_data)
REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool CollectAndCreateNewInstances(/*out*/RedefinitionDataIter* cur_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
bool AllocateAndRememberNewDexFileCookie(
art::Handle<art::mirror::ClassLoader> source_class_loader,
art::Handle<art::mirror::Object> dex_file_obj,
@@ -234,8 +238,14 @@
void RecordNewMethodAdded();
void RecordNewFieldAdded();
+ void RecordHasVirtualMembers() {
+ has_virtuals_ = true;
+ }
- private:
+ bool HasVirtualMembers() const {
+ return has_virtuals_;
+ }
+
bool IsStructuralRedefinition() const {
DCHECK(!(added_fields_ || added_methods_) || driver_->IsStructuralRedefinition())
<< "added_fields_: " << added_fields_ << " added_methods_: " << added_methods_
@@ -243,6 +253,7 @@
return driver_->IsStructuralRedefinition() && (added_fields_ || added_methods_);
}
+ private:
void UpdateClassStructurally(const RedefinitionDataIter& cur_data)
REQUIRES(art::Locks::mutator_lock_);
@@ -257,6 +268,7 @@
bool added_fields_ = false;
bool added_methods_ = false;
+ bool has_virtuals_ = false;
// Does the class need to be reverified due to verification soft-fails possibly forcing
// interpreter or lock-counting?
@@ -311,6 +323,8 @@
REQUIRES_SHARED(art::Locks::mutator_lock_);
bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool CollectAndCreateNewInstances(RedefinitionDataHolder& holder)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_);
void ReverifyClasses(RedefinitionDataHolder& holder) REQUIRES_SHARED(art::Locks::mutator_lock_);
void UnregisterAllBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
diff --git a/runtime/base/locks.h b/runtime/base/locks.h
index 4b85df0..c3518f3 100644
--- a/runtime/base/locks.h
+++ b/runtime/base/locks.h
@@ -129,6 +129,9 @@
kMutatorLock,
kInstrumentEntrypointsLock,
+ // This is a generic lock level for a top-level lock meant to be gained after having the
+ // UserCodeSuspensionLock.
+ kPostUserCodeSuspensionTopLevelLock,
kUserCodeSuspensionLock,
kZygoteCreationLock,
diff --git a/runtime/gc/allocation_listener.h b/runtime/gc/allocation_listener.h
index a578252..376b524 100644
--- a/runtime/gc/allocation_listener.h
+++ b/runtime/gc/allocation_listener.h
@@ -23,11 +23,13 @@
#include "base/locks.h"
#include "base/macros.h"
#include "gc_root.h"
+#include "handle.h"
#include "obj_ptr.h"
namespace art {
namespace mirror {
+class Class;
class Object;
} // namespace mirror
@@ -39,6 +41,26 @@
public:
virtual ~AllocationListener() {}
+ // An event to allow a listener to intercept and modify an allocation before it takes place.
+ // The listener can change the byte_count and type as they see fit. Extreme caution should be used
+ // when doing so. This can also be used to control allocation occurring on another thread.
+ //
+ // Concurrency guarantees: This might be called multiple times for each single allocation. It's
+ // guaranteed that, between the final call to the callback and the object being visible to
+ // heap-walks there are no suspensions. If a suspension was allowed between these events the
+ // callback will be invoked again after passing the suspend point.
+ //
+ // If the alloc succeeds it is guaranteed there are no suspend-points between the last return of
+ // PreObjectAlloc and the newly allocated object being visible to heap-walks.
+ //
+ // This can also be used to make any last-minute changes to the type or size of the allocation.
+ virtual void PreObjectAllocated(Thread* self ATTRIBUTE_UNUSED,
+ MutableHandle<mirror::Class> type ATTRIBUTE_UNUSED,
+ size_t* byte_count ATTRIBUTE_UNUSED)
+ REQUIRES(!Roles::uninterruptible_) REQUIRES_SHARED(Locks::mutator_lock_) {}
+ // Fast check if we want to get the PreObjectAllocated callback, to avoid the expense of creating
+ // handles. Defaults to false.
+ virtual bool HasPreAlloc() const { return false; }
virtual void ObjectAllocated(Thread* self, ObjPtr<mirror::Object>* obj, size_t byte_count)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
};
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index c1b3a63..04632ef 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -65,10 +65,30 @@
HandleWrapperObjPtr<mirror::Class> h = hs.NewHandleWrapper(&klass);
self->PoisonObjectPointers();
}
+ auto send_pre_object_allocated = [&]() REQUIRES_SHARED(Locks::mutator_lock_)
+ ACQUIRE(Roles::uninterruptible_) {
+ if constexpr (kInstrumented) {
+ AllocationListener* l = nullptr;
+ l = alloc_listener_.load(std::memory_order_seq_cst);
+ if (UNLIKELY(l != nullptr) && UNLIKELY(l->HasPreAlloc())) {
+ StackHandleScope<1> hs(self);
+ HandleWrapperObjPtr<mirror::Class> h_klass(hs.NewHandleWrapper(&klass));
+ l->PreObjectAllocated(self, h_klass, &byte_count);
+ }
+ }
+ return self->StartAssertNoThreadSuspension("Called PreObjectAllocated, no suspend until alloc");
+ };
+ // Do the initial pre-alloc
+ const char* old_cause = send_pre_object_allocated();
+ // We shouldn't have any NoThreadSuspension here!
+ DCHECK(old_cause == nullptr) << old_cause;
+
// Need to check that we aren't the large object allocator since the large object allocation code
// path includes this function. If we didn't check we would have an infinite loop.
ObjPtr<mirror::Object> obj;
if (kCheckLargeObject && UNLIKELY(ShouldAllocLargeObject(klass, byte_count))) {
+ // AllocLargeObject can suspend and will recall PreObjectAllocated if needed.
+ self->EndAssertNoThreadSuspension(old_cause);
obj = AllocLargeObject<kInstrumented, PreFenceVisitor>(self, &klass, byte_count,
pre_fence_visitor);
if (obj != nullptr) {
@@ -80,6 +100,8 @@
// If the large object allocation failed, try to use the normal spaces (main space,
// non moving space). This can happen if there is significant virtual address space
// fragmentation.
+ // We need to send the PreObjectAllocated again, we might have suspended during our failure.
+ old_cause = send_pre_object_allocated();
}
// bytes allocated for the (individual) object.
size_t bytes_allocated;
@@ -100,6 +122,7 @@
usable_size = bytes_allocated;
no_suspend_pre_fence_visitor(obj, usable_size);
QuasiAtomic::ThreadFenceForConstructor();
+ self->EndAssertNoThreadSuspension(old_cause);
} else if (
!kInstrumented && allocator == kAllocatorTypeRosAlloc &&
(obj = rosalloc_space_->AllocThreadLocal(self, byte_count, &bytes_allocated)) != nullptr &&
@@ -112,6 +135,7 @@
usable_size = bytes_allocated;
no_suspend_pre_fence_visitor(obj, usable_size);
QuasiAtomic::ThreadFenceForConstructor();
+ self->EndAssertNoThreadSuspension(old_cause);
} else {
// Bytes allocated that includes bulk thread-local buffer allocations in addition to direct
// non-TLAB object allocations.
@@ -121,14 +145,19 @@
if (UNLIKELY(obj == nullptr)) {
// AllocateInternalWithGc can cause thread suspension, if someone instruments the entrypoints
// or changes the allocator in a suspend point here, we need to retry the allocation.
+ // It will send the pre-alloc event again.
+ self->EndAssertNoThreadSuspension(old_cause);
obj = AllocateInternalWithGc(self,
allocator,
kInstrumented,
byte_count,
&bytes_allocated,
&usable_size,
- &bytes_tl_bulk_allocated, &klass);
+ &bytes_tl_bulk_allocated,
+ &klass,
+ &old_cause);
if (obj == nullptr) {
+ self->EndAssertNoThreadSuspension(old_cause);
// The only way that we can get a null return if there is no pending exception is if the
// allocator or instrumentation changed.
if (!self->IsExceptionPending()) {
@@ -156,6 +185,7 @@
}
no_suspend_pre_fence_visitor(obj, usable_size);
QuasiAtomic::ThreadFenceForConstructor();
+ self->EndAssertNoThreadSuspension(old_cause);
if (bytes_tl_bulk_allocated > 0) {
size_t num_bytes_allocated_before =
num_bytes_allocated_.fetch_add(bytes_tl_bulk_allocated, std::memory_order_relaxed);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 85b79da..a97ff98 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -17,6 +17,7 @@
#include "heap.h"
#include <limits>
+#include "android-base/thread_annotations.h"
#if defined(__BIONIC__) || defined(__GLIBC__)
#include <malloc.h> // For mallinfo()
#endif
@@ -1723,11 +1724,37 @@
size_t* bytes_allocated,
size_t* usable_size,
size_t* bytes_tl_bulk_allocated,
- ObjPtr<mirror::Class>* klass) {
+ ObjPtr<mirror::Class>* klass,
+ /*out*/const char** old_no_thread_suspend_cause) {
bool was_default_allocator = allocator == GetCurrentAllocator();
// Make sure there is no pending exception since we may need to throw an OOME.
self->AssertNoPendingException();
DCHECK(klass != nullptr);
+ auto release_no_suspend = [&]() RELEASE(Roles::uninterruptible_) {
+ self->EndAssertNoThreadSuspension(*old_no_thread_suspend_cause);
+ };
+ auto send_object_pre_alloc = [&]() ACQUIRE(Roles::uninterruptible_)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (UNLIKELY(instrumented)) {
+ AllocationListener* l = nullptr;
+ l = alloc_listener_.load(std::memory_order_seq_cst);
+ if (UNLIKELY(l != nullptr) && UNLIKELY(l->HasPreAlloc())) {
+ StackHandleScope<1> hs(self);
+ HandleWrapperObjPtr<mirror::Class> h_klass(hs.NewHandleWrapper(klass));
+ l->PreObjectAllocated(self, h_klass, &alloc_size);
+ }
+ }
+ *old_no_thread_suspend_cause =
+ self->StartAssertNoThreadSuspension("Called PreObjectAllocated, no suspend until alloc");
+};
+#define PERFORM_SUSPENDING_OPERATION(op) \
+ [&]() REQUIRES(Roles::uninterruptible_) REQUIRES_SHARED(Locks::mutator_lock_) { \
+ release_no_suspend(); \
+ auto res = (op); \
+ send_object_pre_alloc(); \
+ return res; \
+ }()
+
StackHandleScope<1> hs(self);
HandleWrapperObjPtr<mirror::Class> h(hs.NewHandleWrapper(klass));
// The allocation failed. If the GC is running, block until it completes, and then retry the
@@ -1735,6 +1762,8 @@
collector::GcType last_gc = WaitForGcToComplete(kGcCauseForAlloc, self);
// If we were the default allocator but the allocator changed while we were suspended,
// abort the allocation.
+ // We just waited, call the pre-alloc again.
+ send_object_pre_alloc();
if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
(!instrumented && EntrypointsInstrumented())) {
return nullptr;
@@ -1749,8 +1778,9 @@
}
collector::GcType tried_type = next_gc_type_;
- const bool gc_ran =
- CollectGarbageInternal(tried_type, kGcCauseForAlloc, false) != collector::kGcTypeNone;
+ const bool gc_ran = PERFORM_SUSPENDING_OPERATION(
+ CollectGarbageInternal(tried_type, kGcCauseForAlloc, false) != collector::kGcTypeNone);
+
if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
(!instrumented && EntrypointsInstrumented())) {
return nullptr;
@@ -1769,8 +1799,8 @@
continue;
}
// Attempt to run the collector, if we succeed, re-try the allocation.
- const bool plan_gc_ran =
- CollectGarbageInternal(gc_type, kGcCauseForAlloc, false) != collector::kGcTypeNone;
+ const bool plan_gc_ran = PERFORM_SUSPENDING_OPERATION(
+ CollectGarbageInternal(gc_type, kGcCauseForAlloc, false) != collector::kGcTypeNone);
if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
(!instrumented && EntrypointsInstrumented())) {
return nullptr;
@@ -1800,7 +1830,7 @@
// TODO: Run finalization, but this may cause more allocations to occur.
// We don't need a WaitForGcToComplete here either.
DCHECK(!gc_plan_.empty());
- CollectGarbageInternal(gc_plan_.back(), kGcCauseForAlloc, true);
+ PERFORM_SUSPENDING_OPERATION(CollectGarbageInternal(gc_plan_.back(), kGcCauseForAlloc, true));
if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
(!instrumented && EntrypointsInstrumented())) {
return nullptr;
@@ -1817,7 +1847,8 @@
current_time - last_time_homogeneous_space_compaction_by_oom_ >
min_interval_homogeneous_space_compaction_by_oom_) {
last_time_homogeneous_space_compaction_by_oom_ = current_time;
- HomogeneousSpaceCompactResult result = PerformHomogeneousSpaceCompact();
+ HomogeneousSpaceCompactResult result =
+ PERFORM_SUSPENDING_OPERATION(PerformHomogeneousSpaceCompact());
// Thread suspension could have occurred.
if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
(!instrumented && EntrypointsInstrumented())) {
@@ -1862,9 +1893,13 @@
}
}
}
+#undef PERFORM_SUSPENDING_OPERATION
// If the allocation hasn't succeeded by this point, throw an OOM error.
if (ptr == nullptr) {
+ release_no_suspend();
ThrowOutOfMemoryError(self, alloc_size, allocator);
+ *old_no_thread_suspend_cause =
+ self->StartAssertNoThreadSuspension("Failed allocation fallback");
}
return ptr;
}
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 9ef6af5..6f6cfd1 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -1011,8 +1011,10 @@
size_t* bytes_allocated,
size_t* usable_size,
size_t* bytes_tl_bulk_allocated,
- ObjPtr<mirror::Class>* klass)
+ ObjPtr<mirror::Class>* klass,
+ /*out*/const char** old_no_thread_suspend_cause)
REQUIRES(!Locks::thread_suspend_count_lock_, !*gc_complete_lock_, !*pending_task_lock_)
+ ACQUIRE(Roles::uninterruptible_)
REQUIRES_SHARED(Locks::mutator_lock_);
// Allocate into a specific space.
diff --git a/runtime/offsets.h b/runtime/offsets.h
index 6d1a8e0..2f36fe6 100644
--- a/runtime/offsets.h
+++ b/runtime/offsets.h
@@ -37,6 +37,9 @@
constexpr size_t SizeValue() const {
return val_;
}
+ constexpr bool operator==(Offset o) const {
+ return SizeValue() == o.SizeValue();
+ }
protected:
size_t val_;
diff --git a/test/1983-structural-redefinition-failures/expected.txt b/test/1983-structural-redefinition-failures/expected.txt
index 54e1bcc..40a0914 100644
--- a/test/1983-structural-redefinition-failures/expected.txt
+++ b/test/1983-structural-redefinition-failures/expected.txt
@@ -28,7 +28,7 @@
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 false
+Is Structurally modifiable class java.lang.Integer true
Is Structurally modifiable class java.lang.Number false
Is Structurally modifiable class art.Test1983$NoVirtuals true
Is Structurally modifiable class art.Test1983$WithVirtuals false
diff --git a/test/1994-final-virtual-structural/expected.txt b/test/1994-final-virtual-structural/expected.txt
new file mode 100644
index 0000000..9b74d30
--- /dev/null
+++ b/test/1994-final-virtual-structural/expected.txt
@@ -0,0 +1,5 @@
+Hi!
+Hello world!
+Hej Verden!
+Bonjour le monde!
+こんにちは世界!
diff --git a/test/1994-final-virtual-structural/info.txt b/test/1994-final-virtual-structural/info.txt
new file mode 100644
index 0000000..606c984
--- /dev/null
+++ b/test/1994-final-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/1994-final-virtual-structural/run b/test/1994-final-virtual-structural/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1994-final-virtual-structural/run
@@ -0,0 +1,17 @@
+#!/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.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1994-final-virtual-structural/src/Main.java b/test/1994-final-virtual-structural/src/Main.java
new file mode 100644
index 0000000..3f0cb14
--- /dev/null
+++ b/test/1994-final-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.Test1994.run();
+ }
+}
diff --git a/test/1994-final-virtual-structural/src/art/Redefinition.java b/test/1994-final-virtual-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1994-final-virtual-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1994-final-virtual-structural/src/art/Test1994.java b/test/1994-final-virtual-structural/src/art/Test1994.java
new file mode 100644
index 0000000..9ae7772
--- /dev/null
+++ b/test/1994-final-virtual-structural/src/art/Test1994.java
@@ -0,0 +1,88 @@
+/*
+ * 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 Test1994 {
+
+ public static final class Transform {
+ public void sayHi() {
+ System.out.println("Hi!");
+ }
+ }
+
+ /**
+ * base64 encoded class/dex file for
+ * public static final class Transform {
+ * public void sayHi() {
+ * sayHiEnglish();
+ * sayHiDanish();
+ * sayHiFrance();
+ * sayHiJapan();
+ * }
+ * public void sayHiEnglish() {
+ * System.out.println("Hello world!");
+ * }
+ * public void sayHiDanish() {
+ * System.out.println("Hej Verden!");
+ * }
+ * public void sayHiJapan() {
+ * System.out.println("こんにちは世界!");
+ * }
+ * public void sayHiFrance() {
+ * System.out.println("Bonjour le monde!");
+ * }
+ * }
+ */
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQA87tn3VIDgMrF+Md2W4r58elaMPcSfk2CMBQAAcAAAAHhWNBIAAAAAAAAAAMgEAAAc" +
+ "AAAAcAAAAAkAAADgAAAAAgAAAAQBAAABAAAAHAEAAAgAAAAkAQAAAQAAAGQBAAAIBAAAhAEAAG4C" +
+ "AAB2AgAAiQIAAJYCAACkAgAAvgIAAM4CAADyAgAAEgMAACkDAAA9AwAAUQMAAGUDAAB0AwAAfwMA" +
+ "AIIDAACGAwAAkwMAAJkDAACeAwAApwMAAK4DAAC7AwAAyQMAANYDAADiAwAA6QMAAGEEAAAEAAAA" +
+ "BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAA4AAAAOAAAACAAAAAAAAAAPAAAACAAAAGgCAAAH" +
+ "AAQAEgAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFQAAAAAAAAAWAAAAAAAAABcAAAAAAAAAGAAAAAQA" +
+ "AQATAAAABQAAAAAAAAAAAAAAEQAAAAUAAAAAAAAADAAAALgEAACIBAAAAAAAAAEAAQABAAAASAIA" +
+ "AAQAAABwEAcAAAAOAAEAAQABAAAATAIAAA0AAABuEAMAAABuEAIAAABuEAQAAABuEAUAAAAOAAAA" +
+ "AwABAAIAAABUAgAACAAAAGIAAAAaAQIAbiAGABAADgADAAEAAgAAAFkCAAAIAAAAYgAAABoBAwBu" +
+ "IAYAEAAOAAMAAQACAAAAXgIAAAgAAABiAAAAGgEBAG4gBgAQAA4AAwABAAIAAABjAgAACAAAAGIA" +
+ "AAAaARsAbiAGABAADgADAA4ABQAOPDw8PAAOAA54AAsADngAFAAOeAARAA54AAEAAAAGAAY8aW5p" +
+ "dD4AEUJvbmpvdXIgbGUgbW9uZGUhAAtIZWogVmVyZGVuIQAMSGVsbG8gd29ybGQhABhMYXJ0L1Rl" +
+ "c3QxOTk0JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5OTQ7ACJMZGFsdmlrL2Fubm90YXRpb24vRW5j" +
+ "bG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lvL1By" +
+ "aW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZh" +
+ "L2xhbmcvU3lzdGVtOwANVGVzdDE5OTQuamF2YQAJVHJhbnNmb3JtAAFWAAJWTAALYWNjZXNzRmxh" +
+ "Z3MABG5hbWUAA291dAAHcHJpbnRsbgAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gA" +
+ "C3NheUhpRnJhbmNlAApzYXlIaUphcGFuAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6" +
+ "ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiJjZDkwMDIzOTMwZDk3M2Y1NzcxMWYxZDRmZGFh" +
+ "ZDdhM2U0NzE0NjM3IiwidmVyc2lvbiI6IjEuNy4xNC1kZXYifQAI44GT44KT44Gr44Gh44Gv5LiW" +
+ "55WMIQACAgEZGAECAwIQBBkRFw0AAAEFAIGABIQDAQGcAwEByAMBAegDAQGIBAEBqAQAAAAAAAAC" +
+ "AAAAeQQAAH8EAACsBAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAcAAAAcAAAAAIA" +
+ "AAAJAAAA4AAAAAMAAAACAAAABAEAAAQAAAABAAAAHAEAAAUAAAAIAAAAJAEAAAYAAAABAAAAZAEA" +
+ "AAEgAAAGAAAAhAEAAAMgAAAGAAAASAIAAAEQAAABAAAAaAIAAAIgAAAcAAAAbgIAAAQgAAACAAAA" +
+ "eQQAAAAgAAABAAAAiAQAAAMQAAACAAAAqAQAAAYgAAABAAAAuAQAAAAQAAABAAAAyAQAAA==");
+
+ public static void run() {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest(new Transform());
+ }
+
+ public static void doTest(Transform t) {
+ t.sayHi();
+ Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+ t.sayHi();
+ }
+}
diff --git a/test/1995-final-virtual-structural-multithread/expected.txt b/test/1995-final-virtual-structural-multithread/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/expected.txt
diff --git a/test/1995-final-virtual-structural-multithread/info.txt b/test/1995-final-virtual-structural-multithread/info.txt
new file mode 100644
index 0000000..f9b7bdd
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/info.txt
@@ -0,0 +1,4 @@
+Tests structural redefinition with multiple threads.
+
+Tests that using the structural redefinition while concurrently using the class being redefined
+doesn't cause any unexpected problems.
diff --git a/test/1995-final-virtual-structural-multithread/run b/test/1995-final-virtual-structural-multithread/run
new file mode 100755
index 0000000..421f7b0
--- /dev/null
+++ b/test/1995-final-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/1995-final-virtual-structural-multithread/src/Main.java b/test/1995-final-virtual-structural-multithread/src/Main.java
new file mode 100644
index 0000000..f19358d
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/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.Test1995.run();
+ }
+}
diff --git a/test/1995-final-virtual-structural-multithread/src/art/Redefinition.java b/test/1995-final-virtual-structural-multithread/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1995-final-virtual-structural-multithread/src/art/Test1995.java b/test/1995-final-virtual-structural-multithread/src/art/Test1995.java
new file mode 100644
index 0000000..1ffee60
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/src/art/Test1995.java
@@ -0,0 +1,168 @@
+/*
+ * 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.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+public class Test1995 {
+ private static final int NUM_THREADS = 20;
+
+ public static final 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 final 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(
+"ZGV4CjAzNQCsHrUqkb8cYgT2oYN7HlVbeOxJT/kONRvgBgAAcAAAAHhWNBIAAAAAAAAAABwGAAAl" +
+"AAAAcAAAAAkAAAAEAQAABAAAACgBAAAEAAAAWAEAAAwAAAB4AQAAAQAAANgBAADoBAAA+AEAAEoD" +
+"AABSAwAAVgMAAF4DAABwAwAAfAMAAIkDAACMAwAAkAMAAKoDAAC6AwAA3gMAAP4DAAASBAAAJgQA" +
+"AEEEAABVBAAAZAQAAG8EAAByBAAAfwQAAIcEAACWBAAAnwQAAK8EAADABAAA0AQAAOIEAADoBAAA" +
+"7wQAAPwEAAAKBQAAFwUAACYFAAAwBQAANwUAAK8FAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO" +
+"AAAADwAAABIAAAAGAAAABQAAAAAAAAAHAAAABgAAAEQDAAAGAAAABwAAAAAAAAASAAAACAAAAAAA" +
+"AAAAAAUAFwAAAAAABQAYAAAAAAAFABkAAAAAAAUAGgAAAAAAAwACAAAAAAAAABwAAAAAAAAAHQAA" +
+"AAAAAAAeAAAAAAAAAB8AAAAAAAAAIAAAAAQAAwACAAAABgADAAIAAAAGAAEAFAAAAAYAAAAhAAAA" +
+"BwACABUAAAAHAAAAFgAAAAAAAAARAAAABAAAAAAAAAAQAAAADAYAANUFAAAAAAAABwABAAIAAAAt" +
+"AwAAQQAAAG4QAwAGAAwAbhAEAAYADAFuEAIABgAMAm4QBQAGAAwDcQAKAAAADARuEAsABAAMBCIF" +
+"BgBwEAcABQBuIAgABQAaAAEAbiAIAAUAbiAIABUAbiAIAAUAbiAIACUAbiAIAAUAbiAIADUAGgAA" +
+"AG4gCAAFAG4gCABFAG4QCQAFAAwAEQAAAAIAAQAAAAAAMQMAAAMAAABUEAAAEQAAAAIAAQAAAAAA" +
+"NQMAAAMAAABUEAEAEQAAAAIAAQAAAAAAOQMAAAMAAABUEAIAEQAAAAIAAQAAAAAAPQMAAAMAAABU" +
+"EAMAEQAAAAIAAQABAAAAJAMAABQAAABwEAYAAQAaAAUAWxABABoAAwBbEAIAGgAEAFsQAAAaACQA" +
+"WxADAA4ACQAOPEtLS0sAEAAOABYADgATAA4AHAAOABkADgAAAAABAAAABQAGIGZyb20gAAIsIAAG" +
+"PGluaXQ+ABBCb25qb3VyIGxlIE1vbmRlAApIZWogVmVyZGVuAAtIZWxsbyBXb3JsZAABTAACTEwA" +
+"GExhcnQvVGVzdDE5OTUkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5NTsAIkxkYWx2aWsvYW5ub3Rh" +
+"dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
+"dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
+"bGRlcjsAEkxqYXZhL2xhbmcvVGhyZWFkOwANVGVzdDE5OTUuamF2YQAJVHJhbnNmb3JtAAFWAAth" +
+"Y2Nlc3NGbGFncwAGYXBwZW5kAA1jdXJyZW50VGhyZWFkAAdnZXROYW1lAA5ncmVldGluZ0Rhbmlz" +
+"aAAPZ3JlZXRpbmdFbmdsaXNoAA5ncmVldGluZ0ZyZW5jaAAQZ3JlZXRpbmdKYXBhbmVzZQAEbmFt" +
+"ZQAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gAC3NheUhpRnJlbmNoAA1zYXlIaUph" +
+"cGFuZXNlAAh0b1N0cmluZwAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIs" +
+"Im1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2ZDg4" +
+"YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AB+OBk+OCk+OBq+OBoeOBr+S4lueVjAACAgEi" +
+"GAECAwITBBkbFxEABAEFAAEBAQEBAQEAgYAE7AUBAfgDAQGMBQEBpAUBAbwFAQHUBQAAAAAAAgAA" +
+"AMYFAADMBQAAAAYAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAJQAAAHAAAAACAAAA" +
+"CQAAAAQBAAADAAAABAAAACgBAAAEAAAABAAAAFgBAAAFAAAADAAAAHgBAAAGAAAAAQAAANgBAAAB" +
+"IAAABgAAAPgBAAADIAAABgAAACQDAAABEAAAAQAAAEQDAAACIAAAJQAAAEoDAAAEIAAAAgAAAMYF" +
+"AAAAIAAAAQAAANUFAAADEAAAAgAAAPwFAAAGIAAAAQAAAAwGAAAAEAAAAQAAABwGAAA=");
+
+
+ public static void run() throws Exception {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest();
+ }
+
+ 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) {
+ Transform t = new Transform();
+ results.add(t.sayHi());
+ }
+ }
+
+ public void finish() throws Exception {
+ finish = true;
+ this.join();
+ }
+
+ public void Check() throws Exception {
+ for (String s : results) {
+ if (!s.equals("Hello from " + getName()) &&
+ !s.equals("Hello, null, null, null from " + getName()) &&
+ !s.equals("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/1996-final-override-virtual-structural/expected.txt b/test/1996-final-override-virtual-structural/expected.txt
new file mode 100644
index 0000000..20cd98f
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/expected.txt
@@ -0,0 +1,6 @@
+Not doing anything
+super: Hi this: Hi
+Redefining calling class
+super: Hi this: SALUTATIONS
+Not doing anything
+super: Hi and then this: SALUTATIONS
diff --git a/test/1996-final-override-virtual-structural/info.txt b/test/1996-final-override-virtual-structural/info.txt
new file mode 100644
index 0000000..55adf7c
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition allows one to override a superclass method.
diff --git a/test/1996-final-override-virtual-structural/run b/test/1996-final-override-virtual-structural/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/run
@@ -0,0 +1,17 @@
+#!/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.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1996-final-override-virtual-structural/src/Main.java b/test/1996-final-override-virtual-structural/src/Main.java
new file mode 100644
index 0000000..ade69cf
--- /dev/null
+++ b/test/1996-final-override-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.Test1996.run();
+ }
+}
diff --git a/test/1996-final-override-virtual-structural/src/art/Redefinition.java b/test/1996-final-override-virtual-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1996-final-override-virtual-structural/src/art/Test1996.java b/test/1996-final-override-virtual-structural/src/art/Test1996.java
new file mode 100644
index 0000000..c2b1125
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/src/art/Test1996.java
@@ -0,0 +1,94 @@
+/*
+ * 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 Test1996 {
+
+ public static class SuperTransform {
+ public String hiValue = "Hi";
+ public String sayHi() {
+ return this.hiValue;
+ }
+ }
+ public static final class Transform extends SuperTransform {
+ public void PostTransform() { }
+ public String sayHiTwice(Runnable run) {
+ run.run();
+ return "super: " + super.sayHi() + " this: " + sayHi();
+ }
+ }
+
+ /**
+ * base64 encoded class/dex file for
+ * public static final class Transform extends SuperTransform {
+ * public String myGreeting;
+ * public void PostTransform() {
+ * myGreeting = "SALUTATIONS";
+ * }
+ * public String sayHiTwice(Runnable run) {
+ * run.run();
+ * return "super: " + super.sayHi() + " and then this: " + sayHi();
+ * }
+ * public String sayHi() {
+ * return myGreeting;
+ * }
+ * }
+ */
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQAO4Dwurw97RcUtfH7np7S5RR8gsJYOfmeABQAAcAAAAHhWNBIAAAAAAAAAALwEAAAc" +
+"AAAAcAAAAAkAAADgAAAABAAAAAQBAAABAAAANAEAAAoAAAA8AQAAAQAAAIwBAADUAwAArAEAAHYC" +
+"AACIAgAAkAIAAJMCAACXAgAAtgIAANACAADgAgAABAMAACQDAAA6AwAATgMAAGkDAAB4AwAAhQMA" +
+"AJQDAACfAwAAogMAAK8DAAC3AwAAwwMAAMkDAADOAwAA1QMAAOEDAADqAwAA9AMAAPsDAAAEAAAA" +
+"BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAABAAAAACAAAABgAAAAAAAAADAAAABgAAAGgCAAAD" +
+"AAAABwAAAHACAAAQAAAACAAAAAAAAAABAAYAEwAAAAAAAwABAAAAAAAAABYAAAABAAMAAQAAAAEA" +
+"AwAMAAAAAQAAABYAAAABAAEAFwAAAAUAAwAVAAAABwADAAEAAAAHAAIAEgAAAAcAAAAZAAAAAQAA" +
+"ABEAAAAAAAAAAAAAAA4AAACsBAAAggQAAAAAAAACAAEAAAAAAFsCAAADAAAAVBAAABEAAAAFAAIA" +
+"AgAAAF8CAAAlAAAAchAGAAQAbxABAAMADARuEAQAAwAMACIBBwBwEAcAAQAaAhgAbiAIACEAbiAI" +
+"AEEAGgQAAG4gCABBAG4gCAABAG4QCQABAAwEEQQAAAEAAQABAAAAUgIAAAQAAABwEAAAAAAOAAIA" +
+"AQAAAAAAVgIAAAUAAAAaAA0AWxAAAA4ACgAOAA0ADksAFAAOABABAA48AAAAAAEAAAAFAAAAAQAA" +
+"AAYAECBhbmQgdGhlbiB0aGlzOiAABjxpbml0PgABTAACTEwAHUxhcnQvVGVzdDE5OTYkU3VwZXJU" +
+"cmFuc2Zvcm07ABhMYXJ0L1Rlc3QxOTk2JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5OTY7ACJMZGFs" +
+"dmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJD" +
+"bGFzczsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xh" +
+"bmcvU3RyaW5nQnVpbGRlcjsADVBvc3RUcmFuc2Zvcm0AC1NBTFVUQVRJT05TAA1UZXN0MTk5Ni5q" +
+"YXZhAAlUcmFuc2Zvcm0AAVYAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQACm15R3JlZXRpbmcABG5hbWUA" +
+"A3J1bgAFc2F5SGkACnNheUhpVHdpY2UAB3N1cGVyOiAACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsi" +
+"Y29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2N2Iz" +
+"ODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYifQAC" +
+"AwEaGAICBAIRBBkUFw8AAQEDAAECgYAEoAQDAbgEAQGsAwEBxAMAAAAAAAACAAAAcwQAAHkEAACg" +
+"BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAJAAAA4AAAAAMA" +
+"AAAEAAAABAEAAAQAAAABAAAANAEAAAUAAAAKAAAAPAEAAAYAAAABAAAAjAEAAAEgAAAEAAAArAEA" +
+"AAMgAAAEAAAAUgIAAAEQAAACAAAAaAIAAAIgAAAcAAAAdgIAAAQgAAACAAAAcwQAAAAgAAABAAAA" +
+"ggQAAAMQAAACAAAAnAQAAAYgAAABAAAArAQAAAAQAAABAAAAvAQAAA==");
+
+ public static void run() {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest(new Transform());
+ }
+
+ public static void doTest(final Transform t) {
+ System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
+ System.out.println(t.sayHiTwice(
+ () -> {
+ System.out.println("Redefining calling class");
+ Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+ t.PostTransform();
+ }));
+ System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
+ }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index cd66472..069cecb 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1146,6 +1146,9 @@
"1991-hello-structural-retransform",
"1992-retransform-no-such-field",
"1993-fallback-non-structural",
+ "1994-final-virtual-structural",
+ "1995-final-virtual-structural-multithread",
+ "1996-final-override-virtual-structural",
"1997-structural-shadow-method",
"1998-structural-shadow-field"
],