diff options
| -rw-r--r-- | runtime/art_method.cc | 48 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_class.cc | 14 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_redefine.cc | 128 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_redefine.h | 6 | ||||
| -rw-r--r-- | runtime/stack.cc | 12 | ||||
| -rw-r--r-- | test/912-classes/classes.cc | 38 | ||||
| -rw-r--r-- | test/912-classes/src/Main.java | 2 | ||||
| -rwxr-xr-x | test/945-obsolete-native/build | 17 | ||||
| -rw-r--r-- | test/945-obsolete-native/expected.txt | 9 | ||||
| -rw-r--r-- | test/945-obsolete-native/info.txt | 1 | ||||
| -rw-r--r-- | test/945-obsolete-native/obsolete_native.cc | 51 | ||||
| -rwxr-xr-x | test/945-obsolete-native/run | 17 | ||||
| -rw-r--r-- | test/945-obsolete-native/src/Main.java | 77 | ||||
| -rw-r--r-- | test/945-obsolete-native/src/Transform.java | 25 | ||||
| -rw-r--r-- | test/Android.bp | 1 | ||||
| -rw-r--r-- | test/ti-agent/common_load.cc | 1 |
16 files changed, 387 insertions, 60 deletions
diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 4902ad42d7..59e6ac0ba3 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -441,12 +441,56 @@ static uint32_t GetOatMethodIndexFromMethodIndex(const DexFile& dex_file, UNREACHABLE(); } +// We use the method's DexFile and declaring class name to find the OatMethod for an obsolete +// method. This is extremely slow but we need it if we want to be able to have obsolete native +// methods since we need this to find the size of its stack frames. +// +// NB We could (potentially) do this differently and rely on the way the transformation is applied +// in order to use the entrypoint to find this information. However, for debugging reasons (most +// notably making sure that new invokes of obsolete methods fail) we choose to instead get the data +// directly from the dex file. +static const OatFile::OatMethod FindOatMethodFromDexFileFor(ArtMethod* method, bool* found) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(method->IsObsolete() && method->IsNative()); + const DexFile* dex_file = method->GetDexFile(); + + // recreate the class_def_index from the descriptor. + std::string descriptor_storage; + const DexFile::TypeId* declaring_class_type_id = + dex_file->FindTypeId(method->GetDeclaringClass()->GetDescriptor(&descriptor_storage)); + CHECK(declaring_class_type_id != nullptr); + dex::TypeIndex declaring_class_type_index = dex_file->GetIndexForTypeId(*declaring_class_type_id); + const DexFile::ClassDef* declaring_class_type_def = + dex_file->FindClassDef(declaring_class_type_index); + CHECK(declaring_class_type_def != nullptr); + uint16_t declaring_class_def_index = dex_file->GetIndexForClassDef(*declaring_class_type_def); + + size_t oat_method_index = GetOatMethodIndexFromMethodIndex(*dex_file, + declaring_class_def_index, + method->GetDexMethodIndex()); + + OatFile::OatClass oat_class = OatFile::FindOatClass(*dex_file, + declaring_class_def_index, + found); + if (!(*found)) { + return OatFile::OatMethod::Invalid(); + } + return oat_class.GetOatMethod(oat_method_index); +} + static const OatFile::OatMethod FindOatMethodFor(ArtMethod* method, PointerSize pointer_size, bool* found) REQUIRES_SHARED(Locks::mutator_lock_) { - // We shouldn't be calling this with obsolete methods. - DCHECK(!method->IsObsolete()); + if (UNLIKELY(method->IsObsolete())) { + // We shouldn't be calling this with obsolete methods except for native obsolete methods for + // which we need to use the oat method to figure out how large the quick frame is. + DCHECK(method->IsNative()) << "We should only be finding the OatMethod of obsolete methods in " + << "order to allow stack walking. Other obsolete methods should " + << "never need to access this information."; + DCHECK_EQ(pointer_size, kRuntimePointerSize) << "Obsolete method in compiler!"; + return FindOatMethodFromDexFileFor(method, found); + } // Although we overwrite the trampoline of non-static methods, we may get here via the resolution // method for direct methods (or virtual methods made direct). mirror::Class* declaring_class = method->GetDeclaringClass(); diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index fc4b6fe71c..7ca233fb10 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -52,6 +52,7 @@ #include "mirror/class_ext.h" #include "mirror/object_reference.h" #include "mirror/object-inl.h" +#include "mirror/reference.h" #include "runtime.h" #include "runtime_callbacks.h" #include "ScopedLocalRef.h" @@ -463,8 +464,17 @@ struct ClassCallback : public art::ClassLoadCallback { } } + void operator()(art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED, + art::ObjPtr<art::mirror::Reference> reference) const + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::mirror::Object* val = reference->GetReferent(); + if (val == input_) { + reference->SetReferent<false>(output_); + } + } + void VisitRoot(art::mirror::CompressedReference<art::mirror::Object>* root ATTRIBUTE_UNUSED) - const { + const { LOG(FATAL) << "Unreachable"; } @@ -478,7 +488,7 @@ struct ClassCallback : public art::ClassLoadCallback { HeapFixupVisitor* hfv = reinterpret_cast<HeapFixupVisitor*>(arg); // Visit references, not native roots. - obj->VisitReferences<false>(*hfv, art::VoidFunctor()); + obj->VisitReferences<false>(*hfv, *hfv); } private: diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 843fd8c8e4..d767c33282 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -68,6 +68,66 @@ namespace openjdkjvmti { using android::base::StringPrintf; +// A helper that fills in a classes obsolete_methods_ and obsolete_dex_caches_ classExt fields as +// they are created. This ensures that we can always call any method of an obsolete ArtMethod object +// almost as soon as they are created since the GetObsoleteDexCache method will succeed. +class ObsoleteMap { + public: + art::ArtMethod* FindObsoleteVersion(art::ArtMethod* original) + REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) { + auto method_pair = id_map_.find(original); + if (method_pair != id_map_.end()) { + art::ArtMethod* res = obsolete_methods_->GetElementPtrSize<art::ArtMethod*>( + method_pair->second, art::kRuntimePointerSize); + DCHECK(res != nullptr); + DCHECK_EQ(original, res->GetNonObsoleteMethod()); + return res; + } else { + return nullptr; + } + } + + void RecordObsolete(art::ArtMethod* original, art::ArtMethod* obsolete) + REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) { + DCHECK(original != nullptr); + DCHECK(obsolete != nullptr); + int32_t slot = next_free_slot_++; + DCHECK_LT(slot, obsolete_methods_->GetLength()); + DCHECK(nullptr == + obsolete_methods_->GetElementPtrSize<art::ArtMethod*>(slot, art::kRuntimePointerSize)); + DCHECK(nullptr == obsolete_dex_caches_->Get(slot)); + obsolete_methods_->SetElementPtrSize(slot, obsolete, art::kRuntimePointerSize); + obsolete_dex_caches_->Set(slot, original_dex_cache_); + id_map_.insert({original, slot}); + } + + ObsoleteMap(art::ObjPtr<art::mirror::PointerArray> obsolete_methods, + art::ObjPtr<art::mirror::ObjectArray<art::mirror::DexCache>> obsolete_dex_caches, + art::ObjPtr<art::mirror::DexCache> original_dex_cache) + : next_free_slot_(0), + obsolete_methods_(obsolete_methods), + obsolete_dex_caches_(obsolete_dex_caches), + original_dex_cache_(original_dex_cache) { + // Figure out where the first unused slot in the obsolete_methods_ array is. + while (obsolete_methods_->GetElementPtrSize<art::ArtMethod*>( + next_free_slot_, art::kRuntimePointerSize) != nullptr) { + DCHECK(obsolete_dex_caches_->Get(next_free_slot_) != nullptr); + next_free_slot_++; + } + // Sanity check that the same slot in obsolete_dex_caches_ is free. + DCHECK(obsolete_dex_caches_->Get(next_free_slot_) == nullptr); + } + + private: + int32_t next_free_slot_; + std::unordered_map<art::ArtMethod*, int32_t> id_map_; + // Pointers to the fields in mirror::ClassExt. These can be held as ObjPtr since this is only used + // when we have an exclusive mutator_lock_ (i.e. all threads are suspended). + art::ObjPtr<art::mirror::PointerArray> obsolete_methods_; + art::ObjPtr<art::mirror::ObjectArray<art::mirror::DexCache>> obsolete_dex_caches_; + art::ObjPtr<art::mirror::DexCache> original_dex_cache_; +}; + // This visitor walks thread stacks and allocates and sets up the obsolete methods. It also does // some basic sanity checks that the obsolete method is sane. class ObsoleteMethodStackVisitor : public art::StackVisitor { @@ -76,7 +136,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { art::Thread* thread, art::LinearAlloc* allocator, const std::unordered_set<art::ArtMethod*>& obsoleted_methods, - /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps) + ObsoleteMap* obsolete_maps) : StackVisitor(thread, /*context*/nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), @@ -94,7 +154,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { art::Thread* thread, art::LinearAlloc* allocator, const std::unordered_set<art::ArtMethod*>& obsoleted_methods, - /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps) + ObsoleteMap* obsolete_maps) REQUIRES(art::Locks::mutator_lock_) { ObsoleteMethodStackVisitor visitor(thread, allocator, @@ -104,6 +164,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { } bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + art::ScopedAssertNoThreadSuspension snts("Fixing up the stack for obsolete methods."); art::ArtMethod* old_method = GetMethod(); if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) { // We cannot ensure that the right dex file is used in inlined frames so we don't support @@ -113,9 +174,8 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { // TODO We should really support redefining intrinsics. // We don't support intrinsics so check for them here. DCHECK(!old_method->IsIntrinsic()); - art::ArtMethod* new_obsolete_method = nullptr; - auto obsolete_method_pair = obsolete_maps_->find(old_method); - if (obsolete_method_pair == obsolete_maps_->end()) { + art::ArtMethod* new_obsolete_method = obsolete_maps_->FindObsoleteVersion(old_method); + if (new_obsolete_method == nullptr) { // Create a new Obsolete Method and put it in the list. art::Runtime* runtime = art::Runtime::Current(); art::ClassLinker* cl = runtime->GetClassLinker(); @@ -129,7 +189,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass()); new_obsolete_method->SetIsObsolete(); new_obsolete_method->SetDontCompile(); - obsolete_maps_->insert({old_method, new_obsolete_method}); + obsolete_maps_->RecordObsolete(old_method, new_obsolete_method); // Update JIT Data structures to point to the new method. art::jit::Jit* jit = art::Runtime::Current()->GetJit(); if (jit != nullptr) { @@ -137,8 +197,6 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { // structures to keep track of the new obsolete method. jit->GetCodeCache()->MoveObsoleteMethod(old_method, new_obsolete_method); } - } else { - new_obsolete_method = obsolete_method_pair->second; } DCHECK(new_obsolete_method != nullptr); SetMethod(new_obsolete_method); @@ -152,9 +210,9 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { // The set of all methods which could be obsoleted. const std::unordered_set<art::ArtMethod*>& obsoleted_methods_; // A map from the original to the newly allocated obsolete method for frames on this thread. The - // values in this map must be added to the obsolete_methods_ (and obsolete_dex_caches_) fields of - // the redefined classes ClassExt by the caller. - std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps_; + // values in this map are added to the obsolete_methods_ (and obsolete_dex_caches_) fields of + // the redefined classes ClassExt as it is filled. + ObsoleteMap* obsolete_maps_; }; jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED, @@ -431,11 +489,12 @@ art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFi } struct CallbackCtx { + ObsoleteMap* obsolete_map; art::LinearAlloc* allocator; - std::unordered_map<art::ArtMethod*, art::ArtMethod*> obsolete_map; std::unordered_set<art::ArtMethod*> obsolete_methods; - explicit CallbackCtx(art::LinearAlloc* alloc) : allocator(alloc) {} + explicit CallbackCtx(ObsoleteMap* map, art::LinearAlloc* alloc) + : obsolete_map(map), allocator(alloc) {} }; void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS { @@ -443,7 +502,7 @@ void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SA ObsoleteMethodStackVisitor::UpdateObsoleteFrames(t, data->allocator, data->obsolete_methods, - &data->obsolete_map); + data->obsolete_map); } // This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is @@ -454,9 +513,18 @@ void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(art::mirror::C art::mirror::ClassExt* ext = art_klass->GetExtData(); CHECK(ext->GetObsoleteMethods() != nullptr); art::ClassLinker* linker = driver_->runtime_->GetClassLinker(); - CallbackCtx ctx(linker->GetAllocatorForClassLoader(art_klass->GetClassLoader())); + // This holds pointers to the obsolete methods map fields which are updated as needed. + ObsoleteMap map(ext->GetObsoleteMethods(), ext->GetObsoleteDexCaches(), art_klass->GetDexCache()); + CallbackCtx ctx(&map, linker->GetAllocatorForClassLoader(art_klass->GetClassLoader())); // Add all the declared methods to the map for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) { + // It is possible to simply filter out some methods where they cannot really become obsolete, + // such as native methods and keep their original (possibly optimized) implementations. We don't + // do this, however, since we would need to mark these functions (still in the classes + // declared_methods array) as obsolete so we will find the correct dex file to get meta-data + // from (for example about stack-frame size). Furthermore we would be unable to get some useful + // error checking from the interpreter which ensure we don't try to start executing obsolete + // methods. ctx.obsolete_methods.insert(&m); // TODO Allow this or check in IsModifiableClass. DCHECK(!m.IsIntrinsic()); @@ -466,36 +534,6 @@ void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(art::mirror::C art::ThreadList* list = art::Runtime::Current()->GetThreadList(); list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx)); } - FillObsoleteMethodMap(art_klass, ctx.obsolete_map); -} - -// Fills the obsolete method map in the art_klass's extData. This is so obsolete methods are able to -// figure out their DexCaches. -void Redefiner::ClassRedefinition::FillObsoleteMethodMap( - art::mirror::Class* art_klass, - const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) { - int32_t index = 0; - art::mirror::ClassExt* ext_data = art_klass->GetExtData(); - art::mirror::PointerArray* obsolete_methods = ext_data->GetObsoleteMethods(); - art::mirror::ObjectArray<art::mirror::DexCache>* obsolete_dex_caches = - ext_data->GetObsoleteDexCaches(); - int32_t num_method_slots = obsolete_methods->GetLength(); - // Find the first empty index. - for (; index < num_method_slots; index++) { - if (obsolete_methods->GetElementPtrSize<art::ArtMethod*>( - index, art::kRuntimePointerSize) == nullptr) { - break; - } - } - // Make sure we have enough space. - CHECK_GT(num_method_slots, static_cast<int32_t>(obsoletes.size() + index)); - CHECK(obsolete_dex_caches->Get(index) == nullptr); - // Fill in the map. - for (auto& obs : obsoletes) { - obsolete_methods->SetElementPtrSize(index, obs.second, art::kRuntimePointerSize); - obsolete_dex_caches->Set(index, art_klass->GetDexCache()); - index++; - } } // Try and get the declared method. First try to get a virtual method then a direct method if that's diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index c441377b10..65ee2912e2 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -155,12 +155,6 @@ class Redefiner { void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) REQUIRES(art::Locks::mutator_lock_); - void FillObsoleteMethodMap( - art::mirror::Class* art_klass, - const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) - REQUIRES(art::Locks::mutator_lock_); - - // Checks that the dex file contains only the single expected class and that the top-level class // data has not been modified in an incompatible manner. bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_); diff --git a/runtime/stack.cc b/runtime/stack.cc index d7ba1d75d8..51a24e4e01 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -874,9 +874,13 @@ void StackVisitor::WalkStack(bool include_transitions) { CHECK_EQ(GetMethod(), callee) << "Expected: " << ArtMethod::PrettyMethod(callee) << " Found: " << ArtMethod::PrettyMethod(GetMethod()); } else { - CHECK_EQ(instrumentation_frame.method_, GetMethod()) - << "Expected: " << ArtMethod::PrettyMethod(instrumentation_frame.method_) - << " Found: " << ArtMethod::PrettyMethod(GetMethod()); + // Instrumentation generally doesn't distinguish between a method's obsolete and + // non-obsolete version. + CHECK_EQ(instrumentation_frame.method_->GetNonObsoleteMethod(), + GetMethod()->GetNonObsoleteMethod()) + << "Expected: " + << ArtMethod::PrettyMethod(instrumentation_frame.method_->GetNonObsoleteMethod()) + << " Found: " << ArtMethod::PrettyMethod(GetMethod()->GetNonObsoleteMethod()); } if (num_frames_ != 0) { // Check agreement of frame Ids only if num_frames_ is computed to avoid infinite @@ -903,7 +907,7 @@ void StackVisitor::WalkStack(bool include_transitions) { << " native=" << method->IsNative() << std::noboolalpha << " entrypoints=" << method->GetEntryPointFromQuickCompiledCode() - << "," << method->GetEntryPointFromJni() + << "," << (method->IsNative() ? method->GetEntryPointFromJni() : nullptr) << " next=" << *cur_quick_frame_; } diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index c92e49f0eb..3ccfe86bed 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -433,9 +433,13 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isLoadedClass( class ClassLoadPrepareEquality { public: static constexpr const char* kClassName = "LMain$ClassE;"; - static constexpr const char* kStorageClassName = "Main$ClassF"; static constexpr const char* kStorageFieldName = "STATIC"; static constexpr const char* kStorageFieldSig = "Ljava/lang/Object;"; + static constexpr const char* kStorageWeakFieldName = "WEAK"; + static constexpr const char* kStorageWeakFieldSig = "Ljava/lang/ref/Reference;"; + static constexpr const char* kWeakClassName = "java/lang/ref/WeakReference"; + static constexpr const char* kWeakInitSig = "(Ljava/lang/Object;)V"; + static constexpr const char* kWeakGetSig = "()Ljava/lang/Object;"; static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, JNIEnv* jni_env, @@ -472,6 +476,8 @@ class ClassLoadPrepareEquality { static void SetOrCompare(JNIEnv* jni_env, jobject value, bool set) { CHECK(storage_class_ != nullptr); + + // Simple direct storage. jfieldID field = jni_env->GetStaticFieldID(storage_class_, kStorageFieldName, kStorageFieldSig); CHECK(field != nullptr); @@ -482,6 +488,36 @@ class ClassLoadPrepareEquality { ScopedLocalRef<jobject> stored(jni_env, jni_env->GetStaticObjectField(storage_class_, field)); CHECK(jni_env->IsSameObject(value, stored.get())); } + + // Storage as a reference. + ScopedLocalRef<jclass> weak_ref_class(jni_env, jni_env->FindClass(kWeakClassName)); + CHECK(weak_ref_class.get() != nullptr); + jfieldID weak_field = jni_env->GetStaticFieldID(storage_class_, + kStorageWeakFieldName, + kStorageWeakFieldSig); + CHECK(weak_field != nullptr); + if (set) { + // Create a WeakReference. + jmethodID weak_init = jni_env->GetMethodID(weak_ref_class.get(), "<init>", kWeakInitSig); + CHECK(weak_init != nullptr); + ScopedLocalRef<jobject> weak_obj(jni_env, jni_env->NewObject(weak_ref_class.get(), + weak_init, + value)); + CHECK(weak_obj.get() != nullptr); + jni_env->SetStaticObjectField(storage_class_, weak_field, weak_obj.get()); + CHECK(!jni_env->ExceptionCheck()); + } else { + // Check the reference value. + jmethodID get_referent = jni_env->GetMethodID(weak_ref_class.get(), "get", kWeakGetSig); + CHECK(get_referent != nullptr); + ScopedLocalRef<jobject> weak_obj(jni_env, jni_env->GetStaticObjectField(storage_class_, + weak_field)); + CHECK(weak_obj.get() != nullptr); + ScopedLocalRef<jobject> weak_referent(jni_env, jni_env->CallObjectMethod(weak_obj.get(), + get_referent)); + CHECK(weak_referent.get() != nullptr); + CHECK(jni_env->IsSameObject(value, weak_referent.get())); + } } static void CheckFound() { diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java index 52a5194138..005074f8c1 100644 --- a/test/912-classes/src/Main.java +++ b/test/912-classes/src/Main.java @@ -14,6 +14,7 @@ * limitations under the License. */ +import java.lang.ref.Reference; import java.lang.reflect.Constructor; import java.lang.reflect.Proxy; import java.util.Arrays; @@ -433,6 +434,7 @@ public class Main { public static class ClassF { public static Object STATIC = null; + public static Reference<Object> WEAK = null; } private static final String DEX1 = System.getenv("DEX_LOCATION") + "/912-classes.jar"; diff --git a/test/945-obsolete-native/build b/test/945-obsolete-native/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/945-obsolete-native/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/945-obsolete-native/expected.txt b/test/945-obsolete-native/expected.txt new file mode 100644 index 0000000000..83efda144d --- /dev/null +++ b/test/945-obsolete-native/expected.txt @@ -0,0 +1,9 @@ +hello +Not doing anything here +goodbye +hello +transforming calling function +goodbye +Hello - Transformed +Not doing anything here +Goodbye - Transformed diff --git a/test/945-obsolete-native/info.txt b/test/945-obsolete-native/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/945-obsolete-native/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/945-obsolete-native/obsolete_native.cc b/test/945-obsolete-native/obsolete_native.cc new file mode 100644 index 0000000000..061e7afbbc --- /dev/null +++ b/test/945-obsolete-native/obsolete_native.cc @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/stringprintf.h" + +#include "android-base/stringprintf.h" +#include "base/logging.h" +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedLocalRef.h" +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test945ObsoleteNative { + +extern "C" JNIEXPORT void JNICALL Java_Main_bindTest945ObsoleteNative( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { + BindFunctions(jvmti_env, env, "Transform"); +} + +extern "C" JNIEXPORT void JNICALL Java_Transform_doExecute(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject runnable) { + jclass runnable_klass = env->FindClass("java/lang/Runnable"); + DCHECK(runnable_klass != nullptr); + jmethodID run_method = env->GetMethodID(runnable_klass, "run", "()V"); + env->CallVoidMethod(runnable, run_method); +} + + +} // namespace Test945ObsoleteNative +} // namespace art diff --git a/test/945-obsolete-native/run b/test/945-obsolete-native/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/945-obsolete-native/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 diff --git a/test/945-obsolete-native/src/Main.java b/test/945-obsolete-native/src/Main.java new file mode 100644 index 0000000000..5e2154e9a3 --- /dev/null +++ b/test/945-obsolete-native/src/Main.java @@ -0,0 +1,77 @@ +/* + * 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 { + // class Transform { + // public void sayHi(Runnable r) { + // System.out.println("Hello - Transformed"); + // doExecute(r); + // System.out.println("Goodbye - Transformed"); + // } + // + // private static native void doExecute(Runnable r); + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAIgoACAASCQATABQIABUKABYAFwoABwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" + + "KVYBAAlkb0V4ZWN1dGUBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAe" + + "AQATSGVsbG8gLSBUcmFuc2Zvcm1lZAcAHwwAIAAhDAAPAA4BABVHb29kYnllIC0gVHJhbnNmb3Jt" + + "ZWQBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291" + + "dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxu" + + "AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABwAIAAAAAAADAAAACQAKAAEACwAAAB0AAQABAAAA" + + "BSq3AAGxAAAAAQAMAAAABgABAAAAEQABAA0ADgABAAsAAAA5AAIAAgAAABWyAAISA7YABCu4AAWy" + + "AAISBrYABLEAAAABAAwAAAASAAQAAAATAAgAFAAMABUAFAAWAQoADwAOAAAAAQAQAAAAAgAR"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQB1fZcJR/opPuXacK8mIla5shH0LSg72qJYAwAAcAAAAHhWNBIAAAAAAAAAALgCAAAR" + + "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAUAgAARAEAAKIB" + + "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAABuAgAAggIA" + + "AIcCAACQAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" + + "lAEAAAsAAAAGAAAAnAEAAAUAAQAOAAAAAAAAAAAAAAAAAAEADAAAAAAAAQAQAAAAAQACAA8AAAAC" + + "AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAKUCAAAAAAAAAQABAAEAAACXAgAABAAAAHAQ" + + "BAAAAA4ABAACAAIAAACcAgAAFAAAAGIAAAAbAQIAAABuIAMAEABxEAEAAwBiAAAAGwEBAAAAbiAD" + + "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" + + "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" + + "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" + + "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAAJZG9FeGVjdXRlABJlbWl0" + + "dGVyOiBqYWNrLTQuMjUAA291dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAQAHDoc8hwAAAAIBAICA" + + "BMQCAYoCAAIB3AIADQAAAAAAAAABAAAAAAAAAAEAAAARAAAAcAAAAAIAAAAHAAAAtAAAAAMAAAAD" + + "AAAA0AAAAAQAAAABAAAA9AAAAAUAAAAFAAAA/AAAAAYAAAABAAAAJAEAAAEgAAACAAAARAEAAAEQ" + + "AAACAAAAlAEAAAIgAAARAAAAogEAAAMgAAACAAAAlwIAAAAgAAABAAAApQIAAAAQAAABAAAAuAIA" + + "AA=="); + + public static void main(String[] args) { + bindTest945ObsoleteNative(); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + t.sayHi(() -> { + System.out.println("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + }); + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + private static native void bindTest945ObsoleteNative(); +} diff --git a/test/945-obsolete-native/src/Transform.java b/test/945-obsolete-native/src/Transform.java new file mode 100644 index 0000000000..2b7cc1b3a1 --- /dev/null +++ b/test/945-obsolete-native/src/Transform.java @@ -0,0 +1,25 @@ +/* + * 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(Runnable r) { + System.out.println("hello"); + doExecute(r); + System.out.println("goodbye"); + } + + private static native void doExecute(Runnable r); +} diff --git a/test/Android.bp b/test/Android.bp index d3244a683a..00c890a834 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -274,6 +274,7 @@ art_cc_defaults { "933-misc-events/misc_events.cc", "936-search-onload/search_onload.cc", "944-transform-classloaders/classloader.cc", + "945-obsolete-native/obsolete_native.cc", ], shared_libs: [ "libbase", diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index c5a93568c6..351857d1d9 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -122,6 +122,7 @@ static AgentLib agents[] = { { "942-private-recursive", common_redefine::OnLoad, nullptr }, { "943-private-recursive-jit", common_redefine::OnLoad, nullptr }, { "944-transform-classloaders", common_redefine::OnLoad, nullptr }, + { "945-obsolete-native", common_redefine::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |