diff options
author | 2017-01-19 14:57:28 -0800 | |
---|---|---|
committer | 2017-01-20 11:32:03 -0800 | |
commit | a7e38d8aaacfca85b40b5df654f85c0979968672 (patch) | |
tree | 34322c79fc328a608fc17303453c8ec1f38b9eaa | |
parent | 05778764cb11162b6f3ff72386135ed45a07af33 (diff) |
Use original dex file for retransformation.
The spec requires us to pass the dex file as it appeared before any
retransformation-capable agents had modified it to the
ClassFileLoadHooks when RetransformClasses is called. We do this by
saving the initial dex file bytes into the class as a byte[].
Bug: 32369916
Test: mma -j40 test-art-host
Change-Id: Ic6af3738cd2a831e91ba1144f502fa58b3c333e4
-rw-r--r-- | runtime/class_linker_test.cc | 2 | ||||
-rw-r--r-- | runtime/mirror/class_ext.cc | 5 | ||||
-rw-r--r-- | runtime/mirror/class_ext.h | 8 | ||||
-rw-r--r-- | runtime/openjdkjvmti/Android.bp | 1 | ||||
-rw-r--r-- | runtime/openjdkjvmti/art_jvmti.h | 24 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_class_definition.cc | 55 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_class_definition.h | 77 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_redefine.cc | 195 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_redefine.h | 22 | ||||
-rw-r--r-- | runtime/openjdkjvmti/transform.cc | 42 | ||||
-rw-r--r-- | runtime/openjdkjvmti/transform.h | 1 | ||||
-rwxr-xr-x | test/932-transform-saves/build | 17 | ||||
-rw-r--r-- | test/932-transform-saves/expected.txt | 3 | ||||
-rw-r--r-- | test/932-transform-saves/info.txt | 1 | ||||
-rwxr-xr-x | test/932-transform-saves/run | 19 | ||||
-rw-r--r-- | test/932-transform-saves/src/Main.java | 116 | ||||
-rw-r--r-- | test/932-transform-saves/src/Transform.java | 28 | ||||
-rw-r--r-- | test/Android.run-test.mk | 1 | ||||
-rw-r--r-- | test/ti-agent/common_helper.cc | 5 | ||||
-rw-r--r-- | test/ti-agent/common_load.cc | 1 |
20 files changed, 503 insertions, 120 deletions
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 0341c64969..d98daa51fe 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -612,7 +612,7 @@ struct ClassExtOffsets : public CheckOffsets<mirror::ClassExt> { ClassExtOffsets() : CheckOffsets<mirror::ClassExt>(false, "Ldalvik/system/ClassExt;") { addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_dex_caches_), "obsoleteDexCaches"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_methods_), "obsoleteMethods"); - addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_cache_), "originalDexCache"); + addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_file_bytes_), "originalDexFile"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, verify_error_), "verifyError"); } }; diff --git a/runtime/mirror/class_ext.cc b/runtime/mirror/class_ext.cc index 7c6a710cef..efd949e031 100644 --- a/runtime/mirror/class_ext.cc +++ b/runtime/mirror/class_ext.cc @@ -113,6 +113,11 @@ void ClassExt::SetVerifyError(ObjPtr<Object> err) { } } +void ClassExt::SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) { + DCHECK(!Runtime::Current()->IsActiveTransaction()); + SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_), bytes); +} + void ClassExt::SetClass(ObjPtr<Class> dalvik_system_ClassExt) { CHECK(dalvik_system_ClassExt != nullptr); dalvik_system_ClassExt_ = GcRoot<Class>(dalvik_system_ClassExt); diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h index 91046314db..ad8a61b676 100644 --- a/runtime/mirror/class_ext.h +++ b/runtime/mirror/class_ext.h @@ -61,6 +61,12 @@ class MANAGED ClassExt : public Object { return GetFieldObject<PointerArray>(OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_methods_)); } + ByteArray* GetOriginalDexFileBytes() REQUIRES_SHARED(Locks::mutator_lock_) { + return GetFieldObject<ByteArray>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_)); + } + + void SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) REQUIRES_SHARED(Locks::mutator_lock_); + void SetObsoleteArrays(ObjPtr<PointerArray> methods, ObjPtr<ObjectArray<DexCache>> dex_caches) REQUIRES_SHARED(Locks::mutator_lock_); @@ -80,7 +86,7 @@ class MANAGED ClassExt : public Object { HeapReference<PointerArray> obsolete_methods_; - HeapReference<DexCache> original_dex_cache_; + HeapReference<ByteArray> original_dex_file_bytes_; // The saved verification error of this class. HeapReference<Object> verify_error_; diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index acdd0d31e9..a731c17d9e 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -21,6 +21,7 @@ cc_defaults { "object_tagging.cc", "OpenjdkJvmTi.cc", "ti_class.cc", + "ti_class_definition.cc", "ti_field.cc", "ti_heap.cc", "ti_jni.cc", diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 1c84d4d0ce..256c3a6cec 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -36,6 +36,7 @@ #include <jni.h> +#include "base/array_slice.h" #include "base/casts.h" #include "base/logging.h" #include "base/macros.h" @@ -125,29 +126,6 @@ static inline jvmtiError CopyString(jvmtiEnv* env, const char* src, unsigned cha return ret; } -struct ArtClassDefinition { - jclass klass; - jobject loader; - std::string name; - jobject protection_domain; - jint dex_len; - JvmtiUniquePtr dex_data; - bool modified; - - ArtClassDefinition() = default; - ArtClassDefinition(ArtClassDefinition&& o) = default; - - void SetNewDexData(ArtJvmTiEnv* env, jint new_dex_len, unsigned char* new_dex_data) { - if (new_dex_data == nullptr) { - return; - } else if (new_dex_data != dex_data.get() || new_dex_len != dex_len) { - modified = true; - dex_len = new_dex_len; - dex_data = MakeJvmtiUniquePtr(env, new_dex_data); - } - } -}; - const jvmtiCapabilities kPotentialCapabilities = { .can_tag_objects = 1, .can_generate_field_modification_events = 0, diff --git a/runtime/openjdkjvmti/ti_class_definition.cc b/runtime/openjdkjvmti/ti_class_definition.cc new file mode 100644 index 0000000000..2c2a79bc58 --- /dev/null +++ b/runtime/openjdkjvmti/ti_class_definition.cc @@ -0,0 +1,55 @@ +/* Copyright (C) 2016 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 "ti_class_definition.h" + +#include "dex_file.h" +#include "handle_scope-inl.h" +#include "handle.h" +#include "mirror/class-inl.h" +#include "mirror/object-inl.h" +#include "thread.h" + +namespace openjdkjvmti { + +bool ArtClassDefinition::IsModified(art::Thread* self) const { + if (modified) { + return true; + } + // Check if the dex file we want to set is the same as the current one. + art::StackHandleScope<1> hs(self); + art::Handle<art::mirror::Class> h_klass(hs.NewHandle(self->DecodeJObject(klass)->AsClass())); + const art::DexFile& cur_dex_file = h_klass->GetDexFile(); + return static_cast<jint>(cur_dex_file.Size()) != dex_len || + memcmp(cur_dex_file.Begin(), dex_data.get(), dex_len) != 0; +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_class_definition.h b/runtime/openjdkjvmti/ti_class_definition.h new file mode 100644 index 0000000000..dbe5da2d63 --- /dev/null +++ b/runtime/openjdkjvmti/ti_class_definition.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2017 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_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_ + +#include "art_jvmti.h" + +namespace openjdkjvmti { + +// A struct that stores data needed for redefining/transforming classes. This structure should only +// even be accessed from a single thread and must not survive past the completion of the +// redefinition/retransformation function that created it. +struct ArtClassDefinition { + public: + jclass klass; + jobject loader; + std::string name; + jobject protection_domain; + jint dex_len; + JvmtiUniquePtr dex_data; + art::ArraySlice<const unsigned char> original_dex_file; + + ArtClassDefinition() = default; + ArtClassDefinition(ArtClassDefinition&& o) = default; + + void SetNewDexData(ArtJvmTiEnv* env, jint new_dex_len, unsigned char* new_dex_data) { + if (new_dex_data == nullptr) { + return; + } else if (new_dex_data != dex_data.get() || new_dex_len != dex_len) { + SetModified(); + dex_len = new_dex_len; + dex_data = MakeJvmtiUniquePtr(env, new_dex_data); + } + } + + void SetModified() { + modified = true; + } + + bool IsModified(art::Thread* self) const REQUIRES_SHARED(art::Locks::mutator_lock_); + + private: + bool modified; +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_ diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 2db8a40ad4..34efc502e1 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -36,6 +36,7 @@ #include "android-base/stringprintf.h" #include "art_jvmti.h" +#include "base/array_slice.h" #include "base/logging.h" #include "dex_file.h" #include "dex_file_types.h" @@ -228,11 +229,17 @@ std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& orig return map; } -Redefiner::ClassRedefinition::ClassRedefinition(Redefiner* driver, - jclass klass, - const art::DexFile* redefined_dex_file, - const char* class_sig) : - driver_(driver), klass_(klass), dex_file_(redefined_dex_file), class_sig_(class_sig) { +Redefiner::ClassRedefinition::ClassRedefinition( + Redefiner* driver, + jclass klass, + const art::DexFile* redefined_dex_file, + const char* class_sig, + art::ArraySlice<const unsigned char> orig_dex_file) : + driver_(driver), + klass_(klass), + dex_file_(redefined_dex_file), + class_sig_(class_sig), + original_dex_file_(orig_dex_file) { GetMirrorClass()->MonitorEnter(driver_->self_); } @@ -280,7 +287,9 @@ jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env, def.dex_len = definitions[i].class_byte_count; def.dex_data = MakeJvmtiUniquePtr(env, class_bytes_copy); // We are definitely modified. - def.modified = true; + def.SetModified(); + def.original_dex_file = art::ArraySlice<const unsigned char>(definitions[i].class_bytes, + definitions[i].class_byte_count); res = Transformer::FillInTransformationData(env, definitions[i].klass, &def); if (res != OK) { return res; @@ -313,12 +322,10 @@ jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env, art::jit::ScopedJitSuspend suspend_jit; // Get shared mutator lock so we can lock all the classes. art::ScopedObjectAccess soa(self); - std::vector<Redefiner::ClassRedefinition> redefinitions; - redefinitions.reserve(definitions.size()); Redefiner r(runtime, self, error_msg); for (const ArtClassDefinition& def : definitions) { // Only try to transform classes that have been modified. - if (def.modified) { + if (def.IsModified(self)) { jvmtiError res = r.AddRedefinition(env, def); if (res != OK) { return res; @@ -371,7 +378,11 @@ jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition return ERR(INVALID_CLASS_FORMAT); } redefinitions_.push_back( - Redefiner::ClassRedefinition(this, def.klass, dex_file.release(), signature_ptr)); + Redefiner::ClassRedefinition(this, + def.klass, + dex_file.release(), + signature_ptr, + def.original_dex_file)); return OK; } @@ -509,44 +520,48 @@ void Redefiner::RecordFailure(jvmtiError result, result_ = result; } -bool Redefiner::ClassRedefinition::FinishRemainingAllocations( - /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader, - /*out*/art::MutableHandle<art::mirror::Object>* java_dex_file_obj, - /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie, - /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) { - art::StackHandleScope<4> hs(driver_->self_); - // This shouldn't allocate - art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader())); - if (loader.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; +// Allocates a ByteArray big enough to store the given number of bytes and copies them from the +// bytes pointer. +static art::mirror::ByteArray* AllocateAndFillBytes(art::Thread* self, + const uint8_t* bytes, + int32_t num_bytes) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::StackHandleScope<1> hs(self); + art::Handle<art::mirror::ByteArray> arr(hs.NewHandle( + art::mirror::ByteArray::Alloc(self, num_bytes))); + if (!arr.IsNull()) { + // Copy it in. Just skip if it's null + memcpy(arr->GetData(), bytes, num_bytes); } - art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader))); - if (dex_file_obj.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; + return arr.Get(); +} + +art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFileBytes() { + // If we have been specifically given a new set of bytes use that + if (original_dex_file_.size() != 0) { + return AllocateAndFillBytes(driver_->self_, + &original_dex_file_.At(0), + original_dex_file_.size()); } - art::Handle<art::mirror::LongArray> new_cookie(hs.NewHandle(AllocateDexFileCookie(dex_file_obj))); - if (new_cookie.Get() == nullptr) { - driver_->self_->AssertPendingOOMException(); - driver_->self_->ClearException(); - RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); - return false; + + // See if we already have one set. + art::ObjPtr<art::mirror::ClassExt> ext(GetMirrorClass()->GetExtData()); + if (!ext.IsNull()) { + art::ObjPtr<art::mirror::ByteArray> old_original_bytes(ext->GetOriginalDexFileBytes()); + if (!old_original_bytes.IsNull()) { + // We do. Use it. + return old_original_bytes.Ptr(); + } } - art::Handle<art::mirror::DexCache> dex_cache(hs.NewHandle(CreateNewDexCache(loader))); - if (dex_cache.Get() == nullptr) { - driver_->self_->AssertPendingOOMException(); - driver_->self_->ClearException(); - RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache"); - return false; + + // Copy the current dex_file + const art::DexFile& current_dex_file = GetMirrorClass()->GetDexFile(); + // TODO Handle this or make it so it cannot happen. + if (current_dex_file.NumClassDefs() != 1) { + LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses " + << "on this class might fail if no transformations are applied to it!"; } - source_class_loader->Assign(loader.Get()); - java_dex_file_obj->Assign(dex_file_obj.Get()); - new_dex_file_cookie->Assign(new_cookie.Get()); - new_dex_cache->Assign(dex_cache.Get()); - return true; + return AllocateAndFillBytes(driver_->self_, current_dex_file.Begin(), current_dex_file.Size()); } struct CallbackCtx { @@ -741,9 +756,10 @@ class RedefinitionDataHolder { kSlotNewDexFileCookie = 2, kSlotNewDexCache = 3, kSlotMirrorClass = 4, + kSlotOrigDexFile = 5, // Must be last one. - kNumSlots = 5, + kNumSlots = 6, }; // This needs to have a HandleScope passed in that is capable of creating a new Handle without @@ -784,6 +800,11 @@ class RedefinitionDataHolder { return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass)); } + art::mirror::ByteArray* GetOriginalDexFileBytes(jint klass_index) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return art::down_cast<art::mirror::ByteArray*>(GetSlot(klass_index, kSlotOrigDexFile)); + } + void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader) REQUIRES_SHARED(art::Locks::mutator_lock_) { SetSlot(klass_index, kSlotSourceClassLoader, loader); @@ -804,6 +825,10 @@ class RedefinitionDataHolder { REQUIRES_SHARED(art::Locks::mutator_lock_) { SetSlot(klass_index, kSlotMirrorClass, klass); } + void SetOriginalDexFileBytes(jint klass_index, art::mirror::ByteArray* bytes) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + SetSlot(klass_index, kSlotOrigDexFile, bytes); + } int32_t Length() REQUIRES_SHARED(art::Locks::mutator_lock_) { return arr_->GetLength() / kNumSlots; @@ -829,6 +854,51 @@ class RedefinitionDataHolder { DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder); }; +bool Redefiner::ClassRedefinition::FinishRemainingAllocations( + int32_t klass_index, /*out*/RedefinitionDataHolder* holder) { + art::StackHandleScope<2> hs(driver_->self_); + holder->SetMirrorClass(klass_index, GetMirrorClass()); + // This shouldn't allocate + art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader())); + holder->SetSourceClassLoader(klass_index, loader.Get()); + if (loader.Get() == nullptr) { + // TODO Better error msg. + RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); + return false; + } + art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader))); + holder->SetJavaDexFile(klass_index, dex_file_obj.Get()); + if (dex_file_obj.Get() == nullptr) { + // TODO Better error msg. + RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); + return false; + } + holder->SetNewDexFileCookie(klass_index, AllocateDexFileCookie(dex_file_obj)); + if (holder->GetNewDexFileCookie(klass_index) == nullptr) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); + return false; + } + holder->SetNewDexCache(klass_index, CreateNewDexCache(loader)); + if (holder->GetNewDexCache(klass_index) == nullptr) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache"); + return false; + } + + // We won't always need to set this field. + holder->SetOriginalDexFileBytes(klass_index, AllocateOrGetOriginalDexFileBytes()); + if (holder->GetOriginalDexFileBytes(klass_index) == nullptr) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file"); + return false; + } + return true; +} + bool Redefiner::CheckAllRedefinitionAreValid() { for (Redefiner::ClassRedefinition& redef : redefinitions_) { if (!redef.CheckRedefinitionIsValid()) { @@ -849,33 +919,11 @@ bool Redefiner::EnsureAllClassAllocationsFinished() { bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) { int32_t cnt = 0; - art::StackHandleScope<4> hs(self_); - art::MutableHandle<art::mirror::Object> java_dex_file(hs.NewHandle<art::mirror::Object>(nullptr)); - art::MutableHandle<art::mirror::ClassLoader> source_class_loader( - hs.NewHandle<art::mirror::ClassLoader>(nullptr)); - art::MutableHandle<art::mirror::LongArray> new_dex_file_cookie( - hs.NewHandle<art::mirror::LongArray>(nullptr)); - art::MutableHandle<art::mirror::DexCache> new_dex_cache( - hs.NewHandle<art::mirror::DexCache>(nullptr)); for (Redefiner::ClassRedefinition& redef : redefinitions_) { - // Reset the out pointers to null - source_class_loader.Assign(nullptr); - java_dex_file.Assign(nullptr); - new_dex_file_cookie.Assign(nullptr); - new_dex_cache.Assign(nullptr); // Allocate the data this redefinition requires. - if (!redef.FinishRemainingAllocations(&source_class_loader, - &java_dex_file, - &new_dex_file_cookie, - &new_dex_cache)) { + if (!redef.FinishRemainingAllocations(cnt, &holder)) { return false; } - // Save the allocated data into the holder. - holder.SetSourceClassLoader(cnt, source_class_loader.Get()); - holder.SetJavaDexFile(cnt, java_dex_file.Get()); - holder.SetNewDexFileCookie(cnt, new_dex_file_cookie.Get()); - holder.SetNewDexCache(cnt, new_dex_cache.Get()); - holder.SetMirrorClass(cnt, redef.GetMirrorClass()); cnt++; } return true; @@ -941,7 +989,7 @@ jvmtiError Redefiner::Run() { redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt)); // TODO Rewrite so we don't do a stack walk for each and every class. redef.FindAndAllocateObsoleteMethods(klass); - redef.UpdateClass(klass, holder.GetNewDexCache(cnt)); + redef.UpdateClass(klass, holder.GetNewDexCache(cnt), holder.GetOriginalDexFileBytes(cnt)); cnt++; } // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have @@ -1034,8 +1082,10 @@ void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> } // Performs updates to class that will allow us to verify it. -void Redefiner::ClassRedefinition::UpdateClass(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache) { +void Redefiner::ClassRedefinition::UpdateClass( + art::ObjPtr<art::mirror::Class> mclass, + art::ObjPtr<art::mirror::DexCache> new_dex_cache, + art::ObjPtr<art::mirror::ByteArray> original_dex_file) { DCHECK_EQ(dex_file_->NumClassDefs(), 1u); const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0); UpdateMethods(mclass, new_dex_cache, class_def); @@ -1047,6 +1097,9 @@ void Redefiner::ClassRedefinition::UpdateClass(art::ObjPtr<art::mirror::Class> m mclass->SetDexCache(new_dex_cache.Ptr()); mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(class_def)); mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str()))); + art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData()); + CHECK(!ext.IsNull()); + ext->SetOriginalDexFileBytes(original_dex_file); } void Redefiner::ClassRedefinition::UpdateJavaDexFile( diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index f8d51ad124..29a7e1f3ac 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -38,6 +38,7 @@ #include "art_jvmti.h" #include "art_method.h" +#include "base/array_slice.h" #include "class_linker.h" #include "dex_file.h" #include "gc_root-inl.h" @@ -56,6 +57,7 @@ #include "obj_ptr.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" +#include "ti_class_definition.h" #include "thread_list.h" #include "transform.h" #include "utf.h" @@ -100,7 +102,8 @@ class Redefiner { ClassRedefinition(Redefiner* driver, jclass klass, const art::DexFile* redefined_dex_file, - const char* class_sig) + const char* class_sig, + art::ArraySlice<const unsigned char> orig_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_); // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor. @@ -111,7 +114,8 @@ class Redefiner { : driver_(other.driver_), klass_(other.klass_), dex_file_(std::move(other.dex_file_)), - class_sig_(std::move(other.class_sig_)) { + class_sig_(std::move(other.class_sig_)), + original_dex_file_(other.original_dex_file_) { other.driver_ = nullptr; } @@ -130,15 +134,15 @@ class Redefiner { art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj) REQUIRES_SHARED(art::Locks::mutator_lock_); + // This may return nullptr with a OOME pending if allocation fails. + art::mirror::ByteArray* AllocateOrGetOriginalDexFileBytes() + REQUIRES_SHARED(art::Locks::mutator_lock_); + void RecordFailure(jvmtiError e, const std::string& err) { driver_->RecordFailure(e, class_sig_, err); } - bool FinishRemainingAllocations( - /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader, - /*out*/art::MutableHandle<art::mirror::Object>* source_dex_file_obj, - /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie, - /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) + bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder) REQUIRES_SHARED(art::Locks::mutator_lock_); void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) @@ -191,7 +195,8 @@ class Redefiner { REQUIRES(art::Locks::mutator_lock_); void UpdateClass(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache) + art::ObjPtr<art::mirror::DexCache> new_dex_cache, + art::ObjPtr<art::mirror::ByteArray> original_dex_file) REQUIRES(art::Locks::mutator_lock_); void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_); @@ -201,6 +206,7 @@ class Redefiner { jclass klass_; std::unique_ptr<const art::DexFile> dex_file_; std::string class_sig_; + art::ArraySlice<const unsigned char> original_dex_file_; }; jvmtiError result_; diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc index 2809cb6926..af4fb7187a 100644 --- a/runtime/openjdkjvmti/transform.cc +++ b/runtime/openjdkjvmti/transform.cc @@ -47,6 +47,7 @@ #include "mem_map.h" #include "mirror/array.h" #include "mirror/class-inl.h" +#include "mirror/class_ext.h" #include "mirror/class_loader-inl.h" #include "mirror/string-inl.h" #include "oat_file.h" @@ -138,28 +139,41 @@ jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* return OK; } -// TODO Implement this for real once transformed dex data is actually saved. +static jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env, + const unsigned char* source, + jint len, + /*out*/unsigned char** dest) { + jvmtiError res = env->Allocate(len, dest); + if (res != OK) { + return res; + } + memcpy(reinterpret_cast<void*>(*dest), + reinterpret_cast<const void*>(source), + len); + return OK; +} + jvmtiError Transformer::GetDexDataForRetransformation(ArtJvmTiEnv* env, art::Handle<art::mirror::Class> klass, /*out*/jint* dex_data_len, /*out*/unsigned char** dex_data) { + art::StackHandleScope<2> hs(art::Thread::Current()); + art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->GetExtData())); + if (!ext.IsNull()) { + art::Handle<art::mirror::ByteArray> orig_dex(hs.NewHandle(ext->GetOriginalDexFileBytes())); + if (!orig_dex.IsNull()) { + *dex_data_len = static_cast<jint>(orig_dex->GetLength()); + return CopyDataIntoJvmtiBuffer(env, + reinterpret_cast<const unsigned char*>(orig_dex->GetData()), + *dex_data_len, + /*out*/dex_data); + } + } // TODO De-quicken the dex file before passing it to the agents. LOG(WARNING) << "Dex file is not de-quickened yet! Quickened dex instructions might be present"; - LOG(WARNING) << "Caching of initial dex data is not yet performed! Dex data might have been " - << "transformed by agent already"; const art::DexFile& dex = klass->GetDexFile(); *dex_data_len = static_cast<jint>(dex.Size()); - unsigned char* new_dex_data = nullptr; - jvmtiError alloc_error = env->Allocate(*dex_data_len, &new_dex_data); - if (alloc_error != OK) { - return alloc_error; - } - // Copy the data into a temporary buffer. - memcpy(reinterpret_cast<void*>(new_dex_data), - reinterpret_cast<const void*>(dex.Begin()), - *dex_data_len); - *dex_data = new_dex_data; - return OK; + return CopyDataIntoJvmtiBuffer(env, dex.Begin(), *dex_data_len, /*out*/dex_data); } // TODO Move this function somewhere more appropriate. diff --git a/runtime/openjdkjvmti/transform.h b/runtime/openjdkjvmti/transform.h index 0ff2bd1d40..65f2ae1353 100644 --- a/runtime/openjdkjvmti/transform.h +++ b/runtime/openjdkjvmti/transform.h @@ -37,6 +37,7 @@ #include <jni.h> #include "art_jvmti.h" +#include "ti_class_definition.h" #include "jvmti.h" namespace openjdkjvmti { diff --git a/test/932-transform-saves/build b/test/932-transform-saves/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/932-transform-saves/build @@ -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-build "$@" --experimental agents diff --git a/test/932-transform-saves/expected.txt b/test/932-transform-saves/expected.txt new file mode 100644 index 0000000000..5097771994 --- /dev/null +++ b/test/932-transform-saves/expected.txt @@ -0,0 +1,3 @@ +hello +Goodbye +hello diff --git a/test/932-transform-saves/info.txt b/test/932-transform-saves/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/932-transform-saves/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/932-transform-saves/run b/test/932-transform-saves/run new file mode 100755 index 0000000000..4379349cb2 --- /dev/null +++ b/test/932-transform-saves/run @@ -0,0 +1,19 @@ +#!/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 "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti diff --git a/test/932-transform-saves/src/Main.java b/test/932-transform-saves/src/Main.java new file mode 100644 index 0000000000..d98ba6dbff --- /dev/null +++ b/test/932-transform-saves/src/Main.java @@ -0,0 +1,116 @@ +/* + * 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. + */ + +import java.util.Base64; +public class Main { + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("hello"); + * } + * } + */ + private static final byte[] CLASS_BYTES_A = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAVoZWxsbwcAGQwAGgAbAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVj" + + "dAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZh" + + "L2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAgAAUABgAA" + + "AAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAABEAAQALAAgAAQAJ" + + "AAAAJQACAAEAAAAJsgACEgO2AASxAAAAAQAKAAAACgACAAAAGgAIABsAAQAMAAAAAgAN"); + private static final byte[] DEX_BYTES_A = Base64.getDecoder().decode( + "ZGV4CjAzNQC6XWInnnDd1H4NdQ3P3inH8eCVmQI6W7LMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAdwEAAI4BAACiAQAAtgEAAMoBAADaAQAA3QEAAOEBAAD1AQAA/AEAAAECAAAKAgAAAQAA" + + "AAIAAAADAAAABAAAAAUAAAAHAAAABwAAAAUAAAAAAAAACAAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAABwCAAAA" + + "AAAAAQABAAEAAAARAgAABAAAAHAQAwAAAA4AAwABAAIAAAAWAgAACQAAAGIAAAAbAQoAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwAS" + + "TGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVt" + + "OwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMjIABWhlbGxvAANvdXQA" + + "B3ByaW50bG4ABXNheUhpABEABw4AGgAHDocAAAABAQCAgASgAgEBuAIAAA0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABECAAAAIAAAAQAAABwCAAAAEAAAAQAAACwCAAA="); + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES_B = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="); + private static final byte[] DEX_BYTES_B = Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); + + public static void main(String[] args) { + System.loadLibrary(args[1]); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + // TODO We currently need to do this transform call since we don't have any way to make the + // original-dex-file a single-class dex-file letting us restore it easily. We should use the + // manipulation library that is being made when we store the original dex file. + // TODO REMOVE this theoretically does nothing but it ensures the original-dex-file we have set + // is one we can return to unaltered. + doCommonClassRedefinition(Transform.class, CLASS_BYTES_A, DEX_BYTES_A); + t.sayHi(); + + // Now turn it into DEX_BYTES_B so it says 'Goodbye' + addCommonTransformationResult("Transform", CLASS_BYTES_B, DEX_BYTES_B); + enableCommonRetransformation(true); + doCommonClassRetransformation(Transform.class); + t.sayHi(); + + // Now turn it back to normal by removing the load-hook and transforming again. + enableCommonRetransformation(false); + doCommonClassRetransformation(Transform.class); + t.sayHi(); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] class_bytes, + byte[] dex_bytes); + private static native void doCommonClassRetransformation(Class<?>... target); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/932-transform-saves/src/Transform.java b/test/932-transform-saves/src/Transform.java new file mode 100644 index 0000000000..8e8af355da --- /dev/null +++ b/test/932-transform-saves/src/Transform.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + // Use lower 'h' to make sure the string will have a different string id + // than the transformation (the transformation code is the same except + // the actual printed String, which was making the test inacurately passing + // in JIT mode when loading the string from the dex cache, as the string ids + // of the two different strings were the same). + // We know the string ids will be different because lexicographically: + // "Goodbye" < "LTransform;" < "hello". + System.out.println("hello"); + } +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index c8e2185891..639996ee7f 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -311,6 +311,7 @@ TEST_ART_BROKEN_TARGET_TESTS += \ 929-search \ 930-hello-retransform \ 931-agent-thread \ + 932-transform-saves \ ifneq (,$(filter target,$(TARGET_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 8799c9188b..4bceef53bb 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -253,11 +253,12 @@ void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env, jint* new_class_data_len, unsigned char** new_class_data) { std::string name_str(name); - if (gTransformations.find(name_str) != gTransformations.end()) { + if (gTransformations.find(name_str) != gTransformations.end() && + gTransformations[name_str].size() > 0) { CommonTransformationResult& res = gTransformations[name_str][0]; const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes; unsigned char* new_data; - jvmti_env->Allocate(desired_array.size(), &new_data); + CHECK_EQ(JVMTI_ERROR_NONE, jvmti_env->Allocate(desired_array.size(), &new_data)); memcpy(new_data, desired_array.data(), desired_array.size()); *new_class_data = new_data; *new_class_data_len = desired_array.size(); diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 1b11442092..f4ce4c381e 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -67,6 +67,7 @@ AgentLib agents[] = { { "921-hello-failure", common_retransform::OnLoad, nullptr }, { "926-multi-obsolescence", common_redefine::OnLoad, nullptr }, { "930-hello-retransform", common_retransform::OnLoad, nullptr }, + { "932-transform-saves", common_retransform::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |