diff options
24 files changed, 694 insertions, 16 deletions
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 9569a91a2d..c472b54b5b 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -599,7 +599,7 @@ class JvmtiFunctions { static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_modifiable_class_ptr) { - return ERR(NOT_IMPLEMENTED); + return Redefiner::IsModifiableClass(env, klass, is_modifiable_class_ptr); } static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr) { @@ -1190,7 +1190,7 @@ class JvmtiFunctions { reinterpret_cast<uint8_t*>(dex_file), &error); if (ret != OK) { - LOG(ERROR) << "FAILURE TO REDEFINE " << error; + LOG(WARNING) << "FAILURE TO REDEFINE " << error; } return ret; } diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 926819d1d0..adec6c94bc 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -37,6 +37,8 @@ #include "art_jvmti.h" #include "base/logging.h" +#include "dex_file.h" +#include "dex_file_types.h" #include "events-inl.h" #include "gc/allocation_listener.h" #include "gc/heap.h" @@ -193,6 +195,45 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { std::string* error_msg_; }; +jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED, + jclass klass, + jboolean* is_redefinable) { + // TODO Check for the appropriate feature flags once we have enabled them. + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::StackHandleScope<1> hs(self); + art::ObjPtr<art::mirror::Object> obj(self->DecodeJObject(klass)); + if (obj.IsNull()) { + return ERR(INVALID_CLASS); + } + art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass())); + std::string err_unused; + *is_redefinable = + Redefiner::GetClassRedefinitionError(h_klass, &err_unused) == OK ? JNI_TRUE : JNI_FALSE; + return OK; +} + +jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> klass, + /*out*/std::string* error_msg) { + if (klass->IsPrimitive()) { + *error_msg = "Modification of primitive classes is not supported"; + return ERR(UNMODIFIABLE_CLASS); + } else if (klass->IsInterface()) { + *error_msg = "Modification of Interface classes is currently not supported"; + return ERR(UNMODIFIABLE_CLASS); + } else if (klass->IsArrayClass()) { + *error_msg = "Modification of Array classes is not supported"; + return ERR(UNMODIFIABLE_CLASS); + } else if (klass->IsProxyClass()) { + *error_msg = "Modification of proxy classes is not supported"; + return ERR(UNMODIFIABLE_CLASS); + } + + // TODO We should check if the class has non-obsoletable methods on the stack + LOG(WARNING) << "presence of non-obsoletable methods on stacks is not currently checked"; + return OK; +} + // Moves dex data to an anonymous, read-only mmap'd region. std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location, jint data_len, @@ -542,6 +583,107 @@ void Redefiner::EnsureObsoleteMethodsAreDeoptimized() { i->ReJitEverything("libOpenJkdJvmti - Class Redefinition"); } +bool Redefiner::CheckClass() { + // TODO Might just want to put it in a ObjPtr and NoSuspend assert. + art::StackHandleScope<1> hs(self_); + // Easy check that only 1 class def is present. + if (dex_file_->NumClassDefs() != 1) { + RecordFailure(ERR(ILLEGAL_ARGUMENT), + StringPrintf("Expected 1 class def in dex file but found %d", + dex_file_->NumClassDefs())); + return false; + } + // Get the ClassDef from the new DexFile. + // Since the dex file has only a single class def the index is always 0. + const art::DexFile::ClassDef& def = dex_file_->GetClassDef(0); + // Get the class as it is now. + art::Handle<art::mirror::Class> current_class(hs.NewHandle(GetMirrorClass())); + + // Check the access flags didn't change. + if (def.GetJavaAccessFlags() != (current_class->GetAccessFlags() & art::kAccValidClassFlags)) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED), + "Cannot change modifiers of class by redefinition"); + return false; + } + + // Check class name. + // These should have been checked by the dexfile verifier on load. + DCHECK_NE(def.class_idx_, art::dex::TypeIndex::Invalid()) << "Invalid type index"; + const char* descriptor = dex_file_->StringByTypeIdx(def.class_idx_); + DCHECK(descriptor != nullptr) << "Invalid dex file structure!"; + if (!current_class->DescriptorEquals(descriptor)) { + std::string storage; + RecordFailure(ERR(NAMES_DONT_MATCH), + StringPrintf("expected file to contain class called '%s' but found '%s'!", + current_class->GetDescriptor(&storage), + descriptor)); + return false; + } + if (current_class->IsObjectClass()) { + if (def.superclass_idx_ != art::dex::TypeIndex::Invalid()) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Superclass added!"); + return false; + } + } else { + const char* super_descriptor = dex_file_->StringByTypeIdx(def.superclass_idx_); + DCHECK(descriptor != nullptr) << "Invalid dex file structure!"; + if (!current_class->GetSuperClass()->DescriptorEquals(super_descriptor)) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Superclass changed"); + return false; + } + } + const art::DexFile::TypeList* interfaces = dex_file_->GetInterfacesList(def); + if (interfaces == nullptr) { + if (current_class->NumDirectInterfaces() != 0) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added"); + return false; + } + } else { + DCHECK(!current_class->IsProxyClass()); + const art::DexFile::TypeList* current_interfaces = current_class->GetInterfaceTypeList(); + if (current_interfaces == nullptr || current_interfaces->Size() != interfaces->Size()) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added or removed"); + return false; + } + // The order of interfaces is (barely) meaningful so we error if it changes. + const art::DexFile& orig_dex_file = current_class->GetDexFile(); + for (uint32_t i = 0; i < interfaces->Size(); i++) { + if (strcmp( + dex_file_->StringByTypeIdx(interfaces->GetTypeItem(i).type_idx_), + orig_dex_file.StringByTypeIdx(current_interfaces->GetTypeItem(i).type_idx_)) != 0) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), + "Interfaces changed or re-ordered"); + return false; + } + } + } + LOG(WARNING) << "No verification is done on annotations of redefined classes."; + + return true; +} + +// TODO Move this to use IsRedefinable when that function is made. +bool Redefiner::CheckRedefinable() { + std::string err; + art::StackHandleScope<1> hs(self_); + + art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass())); + jvmtiError res = Redefiner::GetClassRedefinitionError(h_klass, &err); + if (res != OK) { + RecordFailure(res, err); + return false; + } else { + return true; + } +} + +bool Redefiner::CheckRedefinitionIsValid() { + return CheckRedefinable() && + CheckClass() && + CheckSameFields() && + CheckSameMethods(); +} + jvmtiError Redefiner::Run() { art::StackHandleScope<5> hs(self_); // TODO We might want to have a global lock (or one based on the class being redefined at least) @@ -552,7 +694,7 @@ jvmtiError Redefiner::Run() { // doing a try loop. The other allocations we need to ensure that nothing has changed in the time // between allocating them and pausing all threads before we can update them so we need to do a // try loop. - if (!EnsureRedefinitionIsValid() || !EnsureClassAllocationsFinished()) { + if (!CheckRedefinitionIsValid() || !EnsureClassAllocationsFinished()) { return result_; } art::MutableHandle<art::mirror::ClassLoader> source_class_loader( diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 9d23ce445f..01b5eca330 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -80,6 +80,8 @@ class Redefiner { unsigned char* dex_data, std::string* error_msg); + static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); + private: jvmtiError result_; art::Runtime* runtime_; @@ -106,6 +108,10 @@ class Redefiner { error_msg_(error_msg), class_sig_(class_sig) { } + static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass, + /*out*/std::string* error_msg) + REQUIRES_SHARED(art::Locks::mutator_lock_); + static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, jint data_len, unsigned char* dex_data, @@ -152,14 +158,29 @@ class Redefiner { void RecordFailure(jvmtiError result, const std::string& error_msg); - // TODO Actually write this. // This will check that no constraints are violated (more than 1 class in dex file, any changes in // number/declaration of methods & fields, changes in access flags, etc.) - bool EnsureRedefinitionIsValid() { - LOG(WARNING) << "Redefinition is not checked for validity currently"; + bool CheckRedefinitionIsValid() REQUIRES_SHARED(art::Locks::mutator_lock_); + + // Checks that the class can even be redefined. + bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_); + + // Checks that the dex file does not add/remove methods. + bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_) { + LOG(WARNING) << "methods are not checked for modification currently"; return true; } + // Checks that the dex file does not modify fields + bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_) { + LOG(WARNING) << "Fields are not checked for modification currently"; + return true; + } + + // 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_); + bool UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, art::ObjPtr<art::mirror::LongArray> new_cookie, /*out*/art::ObjPtr<art::mirror::LongArray>* original_cookie) diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index 19d82c544c..38a4f0e337 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -29,6 +29,20 @@ namespace art { namespace Test912Classes { +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isModifiableClass( + JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { + jboolean res = JNI_FALSE; + jvmtiError result = jvmti_env->IsModifiableClass(klass, &res); + if (result != JVMTI_ERROR_NONE) { + char* err; + jvmti_env->GetErrorName(result, &err); + printf("Failure running IsModifiableClass: %s\n", err); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + return JNI_FALSE; + } + return res; +} + extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassSignature( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { char* sig; diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt index 3507a1a676..44c861a3b7 100644 --- a/test/912-classes/expected.txt +++ b/test/912-classes/expected.txt @@ -12,13 +12,13 @@ 411 [[D, null] 411 -int interface=false array=false -$Proxy0 interface=false array=false -java.lang.Runnable interface=true array=false -java.lang.String interface=false array=false -[I interface=false array=true -[Ljava.lang.Runnable; interface=false array=true -[Ljava.lang.String; interface=false array=true +int interface=false array=false modifiable=false +$Proxy0 interface=false array=false modifiable=false +java.lang.Runnable interface=true array=false modifiable=false +java.lang.String interface=false array=false modifiable=true +[I interface=false array=true modifiable=false +[Ljava.lang.Runnable; interface=false array=true modifiable=false +[Ljava.lang.String; interface=false array=true modifiable=false [public static final int java.lang.Integer.BYTES, static final char[] java.lang.Integer.DigitOnes, static final char[] java.lang.Integer.DigitTens, public static final int java.lang.Integer.MAX_VALUE, public static final int java.lang.Integer.MIN_VALUE, public static final int java.lang.Integer.SIZE, private static final java.lang.String[] java.lang.Integer.SMALL_NEG_VALUES, private static final java.lang.String[] java.lang.Integer.SMALL_NONNEG_VALUES, public static final java.lang.Class java.lang.Integer.TYPE, static final char[] java.lang.Integer.digits, private static final long java.lang.Integer.serialVersionUID, static final int[] java.lang.Integer.sizeTable, private final int java.lang.Integer.value] [] [] diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java index 69e5a4cc58..e627d4227a 100644 --- a/test/912-classes/src/Main.java +++ b/test/912-classes/src/Main.java @@ -107,7 +107,9 @@ public class Main { private static void testClassType(Class<?> c) throws Exception { boolean isInterface = isInterface(c); boolean isArray = isArrayClass(c); - System.out.println(c.getName() + " interface=" + isInterface + " array=" + isArray); + boolean isModifiable = isModifiableClass(c); + System.out.println(c.getName() + " interface=" + isInterface + " array=" + isArray + + " modifiable=" + isModifiable); } private static void testClassFields(Class<?> c) throws Exception { @@ -149,6 +151,7 @@ public class Main { } } + private static native boolean isModifiableClass(Class<?> c); private static native String[] getClassSignature(Class<?> c); private static native boolean isInterface(Class<?> c); diff --git a/test/921-hello-failure/build b/test/921-hello-failure/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/921-hello-failure/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/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt new file mode 100644 index 0000000000..e2665ef30b --- /dev/null +++ b/test/921-hello-failure/expected.txt @@ -0,0 +1,15 @@ +hello - NewName +Transformation error : java.lang.Exception(Failed to redefine class <LTransform;> due to JVMTI_ERROR_NAMES_DONT_MATCH) +hello - NewName +hello - DifferentAccess +Transformation error : java.lang.Exception(Failed to redefine class <LTransform;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED) +hello - DifferentAccess +hello2 - NewInterface +Transformation error : java.lang.Exception(Failed to redefine class <LTransform2;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED) +hello2 - NewInterface +hello2 - MissingInterface +Transformation error : java.lang.Exception(Failed to redefine class <LTransform2;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED) +hello2 - MissingInterface +hello2 - ReorderInterface +Transformation error : java.lang.Exception(Failed to redefine class <LTransform2;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED) +hello2 - ReorderInterface diff --git a/test/921-hello-failure/info.txt b/test/921-hello-failure/info.txt new file mode 100644 index 0000000000..9d241c77b1 --- /dev/null +++ b/test/921-hello-failure/info.txt @@ -0,0 +1,7 @@ +Tests for redefinition failure modes. + +Tests +---- + +- NewName: The name of the class is changed +- DifferentAccess: Class access is changed from <default> to 'public' diff --git a/test/921-hello-failure/run b/test/921-hello-failure/run new file mode 100755 index 0000000000..3ef4832da2 --- /dev/null +++ b/test/921-hello-failure/run @@ -0,0 +1,20 @@ +#!/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/921-hello-failure/src/DifferentAccess.java b/test/921-hello-failure/src/DifferentAccess.java new file mode 100644 index 0000000000..d4b16e0522 --- /dev/null +++ b/test/921-hello-failure/src/DifferentAccess.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Base64; + +class DifferentAccess { + // The following is a base64 encoding of the following class. + // public class NotTransform { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + + "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" + + "aWxlAQAOVHJhbnNmb3JtLmphdmEMAAcACAEAD2phdmEvbGFuZy9FcnJvcgEAFVNob3VsZCBub3Qg" + + "YmUgY2FsbGVkIQwABwAMAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAAhAAUABgAAAAAA" + + "AgABAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAEAAQALAAwAAQAJAAAA" + + "IgADAAIAAAAKuwACWRIDtwAEvwAAAAEACgAAAAYAAQAAAAMAAQANAAAAAgAO"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQANVRT7zleRLG4E5DhtK7OtoDxZlUQMI5eQAgAAcAAAAHhWNBIAAAAAAAAAAPwBAAAL" + + "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACIAQAACAEAAEoB" + + "AABSAQAAXwEAAHIBAACGAQAAmgEAALEBAADBAQAAxAEAAMgBAADcAQAAAQAAAAIAAAADAAAABAAA" + + "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" + + "AAAAAAAAAAABAAAAAgAAAAAAAAAGAAAAAAAAAO4BAAAAAAAAAQABAAEAAADjAQAABAAAAHAQAwAA" + + "AA4ABAACAAIAAADoAQAACQAAACIAAQAbAQUAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgALTFRy" + + "YW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xh" + + "bmcvU3RyaW5nOwAVU2hvdWxkIG5vdCBiZSBjYWxsZWQhAA5UcmFuc2Zvcm0uamF2YQABVgACVkwA" + + "EmVtaXR0ZXI6IGphY2stNC4yMAAFc2F5SGkAAQAHDgADAQAHDgAAAAEBAIGABIgCAQGgAgwAAAAA" + + "AAAAAQAAAAAAAAABAAAACwAAAHAAAAACAAAABQAAAJwAAAADAAAAAgAAALAAAAAFAAAABAAAAMgA" + + "AAAGAAAAAQAAAOgAAAABIAAAAgAAAAgBAAABEAAAAQAAAEQBAAACIAAACwAAAEoBAAADIAAAAgAA" + + "AOMBAAAAIAAAAQAAAO4BAAAAEAAAAQAAAPwBAAA="); + + public static void doTest(Transform t) { + t.sayHi("DifferentAccess"); + try { + Main.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("DifferentAccess"); + } +} diff --git a/test/921-hello-failure/src/Iface1.java b/test/921-hello-failure/src/Iface1.java new file mode 100644 index 0000000000..f53275a656 --- /dev/null +++ b/test/921-hello-failure/src/Iface1.java @@ -0,0 +1,19 @@ +/* + * 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. + */ + +interface Iface1 { + void sayHi(String s); +} diff --git a/test/921-hello-failure/src/Iface2.java b/test/921-hello-failure/src/Iface2.java new file mode 100644 index 0000000000..54cdd901c9 --- /dev/null +++ b/test/921-hello-failure/src/Iface2.java @@ -0,0 +1,19 @@ +/* + * 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. + */ + +interface Iface2 { + void sayHi(String s); +} diff --git a/test/921-hello-failure/src/Iface3.java b/test/921-hello-failure/src/Iface3.java new file mode 100644 index 0000000000..819134d30d --- /dev/null +++ b/test/921-hello-failure/src/Iface3.java @@ -0,0 +1,19 @@ +/* + * 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. + */ + +interface Iface3 { + void sayHi(String s); +} diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java new file mode 100644 index 0000000000..69c48e26cc --- /dev/null +++ b/test/921-hello-failure/src/Main.java @@ -0,0 +1,32 @@ +/* + * 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) { + System.loadLibrary(args[1]); + NewName.doTest(new Transform()); + DifferentAccess.doTest(new Transform()); + NewInterface.doTest(new Transform2()); + MissingInterface.doTest(new Transform2()); + ReorderInterface.doTest(new Transform2()); + } + + // Transforms the class. This throws an exception if something goes wrong. + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile) throws Exception; +} diff --git a/test/921-hello-failure/src/MissingInterface.java b/test/921-hello-failure/src/MissingInterface.java new file mode 100644 index 0000000000..d17a6defbe --- /dev/null +++ b/test/921-hello-failure/src/MissingInterface.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Base64; + +class MissingInterface { + // The following is a base64 encoding of the following class. + // class Transform2 implements Iface1 { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFwoABgAQBwARCAASCgACABMHABQHABUHABYBAAY8aW5pdD4BAAMoKVYBAARDb2Rl" + + "AQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3Vy" + + "Y2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAIAAkBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91bGQg" + + "bm90IGJlIGNhbGxlZCEMAAgADQEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAGSWZh" + + "Y2UxACAABQAGAAEABwAAAAIAAAAIAAkAAQAKAAAAHQABAAEAAAAFKrcAAbEAAAABAAsAAAAGAAEA" + + "AAABAAEADAANAAEACgAAACIAAwACAAAACrsAAlkSA7cABL8AAAABAAsAAAAGAAEAAAADAAEADgAA" + + "AAIADw=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDiWVay8/Z0/tXQaTTI+QtwTM65gRJVMOusAgAAcAAAAHhWNBIAAAAAAAAAABgCAAAM" + + "AAAAcAAAAAYAAACgAAAAAgAAALgAAAAAAAAAAAAAAAQAAADQAAAAAQAAAPAAAACcAQAAEAEAAFoB" + + "AABiAQAAbAEAAHoBAACNAQAAoQEAALUBAADMAQAA3QEAAOABAADkAQAA+AEAAAEAAAACAAAAAwAA" + + "AAQAAAAFAAAACAAAAAgAAAAFAAAAAAAAAAkAAAAFAAAAVAEAAAEAAAAAAAAAAQABAAsAAAACAAEA" + + "AAAAAAMAAAAAAAAAAQAAAAAAAAADAAAATAEAAAcAAAAAAAAACgIAAAAAAAABAAEAAQAAAP8BAAAE" + + "AAAAcBADAAAADgAEAAIAAgAAAAQCAAAJAAAAIgACABsBBgAAAHAgAgAQACcAAAABAAAAAAAAAAEA" + + "AAAEAAY8aW5pdD4ACExJZmFjZTE7AAxMVHJhbnNmb3JtMjsAEUxqYXZhL2xhbmcvRXJyb3I7ABJM" + + "amF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAVU2hvdWxkIG5vdCBiZSBjYWxs" + + "ZWQhAA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMjAABXNheUhpAAEA" + + "Bw4AAwEABw4AAAABAQCAgASQAgEBqAIMAAAAAAAAAAEAAAAAAAAAAQAAAAwAAABwAAAAAgAAAAYA" + + "AACgAAAAAwAAAAIAAAC4AAAABQAAAAQAAADQAAAABgAAAAEAAADwAAAAASAAAAIAAAAQAQAAARAA" + + "AAIAAABMAQAAAiAAAAwAAABaAQAAAyAAAAIAAAD/AQAAACAAAAEAAAAKAgAAABAAAAEAAAAYAgAA"); + + public static void doTest(Transform2 t) { + t.sayHi("MissingInterface"); + try { + Main.doCommonClassRedefinition(Transform2.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("MissingInterface"); + } +} diff --git a/test/921-hello-failure/src/NewInterface.java b/test/921-hello-failure/src/NewInterface.java new file mode 100644 index 0000000000..fe7722261a --- /dev/null +++ b/test/921-hello-failure/src/NewInterface.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Base64; + +class NewInterface { + // The following is a base64 encoding of the following class. + // class Transform2 implements Iface1, Iface2, Iface3 { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAGwoABgASBwATCAAUCgACABUHABYHABcHABgHABkHABoBAAY8aW5pdD4BAAMoKVYB" + + "AARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYB" + + "AApTb3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAKAAsBAA9qYXZhL2xhbmcvRXJyb3IBABVT" + + "aG91bGQgbm90IGJlIGNhbGxlZCEMAAoADwEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0" + + "AQAGSWZhY2UxAQAGSWZhY2UyAQAGSWZhY2UzACAABQAGAAMABwAIAAkAAAACAAAACgALAAEADAAA" + + "AB0AAQABAAAABSq3AAGxAAAAAQANAAAABgABAAAAAQABAA4ADwABAAwAAAAiAAMAAgAAAAq7AAJZ" + + "EgO3AAS/AAAAAQANAAAABgABAAAAAwABABAAAAACABE="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCBWnko4SMXeuXSO3fGJBp0WSlc0HLRr63UAgAAcAAAAHhWNBIAAAAAAAAAAEACAAAO" + + "AAAAcAAAAAgAAACoAAAAAgAAAMgAAAAAAAAAAAAAAAQAAADgAAAAAQAAAAABAAC0AQAAIAEAAG4B" + + "AAB2AQAAgAEAAIoBAACUAQAAogEAALUBAADJAQAA3QEAAPQBAAAFAgAACAIAAAwCAAAgAgAAAQAA" + + "AAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAoAAAAKAAAABwAAAAAAAAALAAAABwAAAGgBAAADAAAA" + + "AAAAAAMAAQANAAAABAABAAAAAAAFAAAAAAAAAAMAAAAAAAAABQAAAFwBAAAJAAAAAAAAADICAAAA" + + "AAAAAQABAAEAAAAnAgAABAAAAHAQAwAAAA4ABAACAAIAAAAsAgAACQAAACIABAAbAQgAAABwIAIA" + + "EAAnAAAAAwAAAAAAAQACAAAAAQAAAAYABjxpbml0PgAITElmYWNlMTsACExJZmFjZTI7AAhMSWZh" + + "Y2UzOwAMTFRyYW5zZm9ybTI7ABFMamF2YS9sYW5nL0Vycm9yOwASTGphdmEvbGFuZy9PYmplY3Q7" + + "ABJMamF2YS9sYW5nL1N0cmluZzsAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAPVHJhbnNmb3JtMi5q" + + "YXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00LjIwAAVzYXlIaQABAAcOAAMBAAcOAAAAAQEAgIAE" + + "oAIBAbgCDAAAAAAAAAABAAAAAAAAAAEAAAAOAAAAcAAAAAIAAAAIAAAAqAAAAAMAAAACAAAAyAAA" + + "AAUAAAAEAAAA4AAAAAYAAAABAAAAAAEAAAEgAAACAAAAIAEAAAEQAAACAAAAXAEAAAIgAAAOAAAA" + + "bgEAAAMgAAACAAAAJwIAAAAgAAABAAAAMgIAAAAQAAABAAAAQAIAAA=="); + + public static void doTest(Transform2 t) { + t.sayHi("NewInterface"); + try { + Main.doCommonClassRedefinition(Transform2.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("NewInterface"); + } +} diff --git a/test/921-hello-failure/src/NewName.java b/test/921-hello-failure/src/NewName.java new file mode 100644 index 0000000000..a6f249a2ef --- /dev/null +++ b/test/921-hello-failure/src/NewName.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Base64; + +class NewName { + // class NotTransform { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + + "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" + + "aWxlAQARTm90VHJhbnNmb3JtLmphdmEMAAcACAEAD2phdmEvbGFuZy9FcnJvcgEAFVNob3VsZCBu" + + "b3QgYmUgY2FsbGVkIQwABwAMAQAMTm90VHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAAgAAUA" + + "BgAAAAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAEAAQALAAwA" + + "AQAJAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEACgAAAAYAAQAAAAMAAQANAAAAAgAO"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDLV95i5xnv6iUi6uIeDoY5jP5Xe9NP1AiYAgAAcAAAAHhWNBIAAAAAAAAAAAQCAAAL" + + "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACQAQAACAEAAEoB" + + "AABSAQAAYgEAAHUBAACJAQAAnQEAALABAADHAQAAygEAAM4BAADiAQAAAQAAAAIAAAADAAAABAAA" + + "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" + + "AAAAAAAAAAAAAAAAAgAAAAAAAAAFAAAAAAAAAPQBAAAAAAAAAQABAAEAAADpAQAABAAAAHAQAwAA" + + "AA4ABAACAAIAAADuAQAACQAAACIAAQAbAQYAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgAOTE5v" + + "dFRyYW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh" + + "L2xhbmcvU3RyaW5nOwARTm90VHJhbnNmb3JtLmphdmEAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAB" + + "VgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMAAFc2F5SGkAAQAHDgADAQAHDgAAAAEBAICABIgCAQGg" + + "AgAADAAAAAAAAAABAAAAAAAAAAEAAAALAAAAcAAAAAIAAAAFAAAAnAAAAAMAAAACAAAAsAAAAAUA" + + "AAAEAAAAyAAAAAYAAAABAAAA6AAAAAEgAAACAAAACAEAAAEQAAABAAAARAEAAAIgAAALAAAASgEA" + + "AAMgAAACAAAA6QEAAAAgAAABAAAA9AEAAAAQAAABAAAABAIAAA=="); + + public static void doTest(Transform t) { + t.sayHi("NewName"); + try { + Main.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("NewName"); + } +} diff --git a/test/921-hello-failure/src/ReorderInterface.java b/test/921-hello-failure/src/ReorderInterface.java new file mode 100644 index 0000000000..ce78dbc241 --- /dev/null +++ b/test/921-hello-failure/src/ReorderInterface.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Base64; + +class ReorderInterface { + // The following is a base64 encoding of the following class. + // class Transform2 implements Iface2, Iface1 { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAGQoABgARBwASCAATCgACABQHABUHABYHABcHABgBAAY8aW5pdD4BAAMoKVYBAARD" + + "b2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApT" + + "b3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAJAAoBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91" + + "bGQgbm90IGJlIGNhbGxlZCEMAAkADgEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAG" + + "SWZhY2UyAQAGSWZhY2UxACAABQAGAAIABwAIAAAAAgAAAAkACgABAAsAAAAdAAEAAQAAAAUqtwAB" + + "sQAAAAEADAAAAAYAAQAAAAEAAQANAA4AAQALAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEADAAA" + + "AAYAAQAAAAMAAQAPAAAAAgAQ"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQChWfUC02YEHJZLC4V4pHrGMdqwD8NnzXvAAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAN" + + "AAAAcAAAAAcAAACkAAAAAgAAAMAAAAAAAAAAAAAAAAQAAADYAAAAAQAAAPgAAACoAQAAGAEAAGIB" + + "AABqAQAAdAEAAH4BAACMAQAAnwEAALMBAADHAQAA3gEAAO8BAADyAQAA9gEAAAoCAAABAAAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAABcAQAAAgAAAAAAAAACAAEA" + + "DAAAAAMAAQAAAAAABAAAAAAAAAACAAAAAAAAAAQAAABUAQAACAAAAAAAAAAcAgAAAAAAAAEAAQAB" + + "AAAAEQIAAAQAAABwEAMAAAAOAAQAAgACAAAAFgIAAAkAAAAiAAMAGwEHAAAAcCACABAAJwAAAAIA" + + "AAABAAAAAQAAAAUABjxpbml0PgAITElmYWNlMTsACExJZmFjZTI7AAxMVHJhbnNmb3JtMjsAEUxq" + + "YXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAV" + + "U2hvdWxkIG5vdCBiZSBjYWxsZWQhAA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBq" + + "YWNrLTQuMjAABXNheUhpAAEABw4AAwEABw4AAAABAQCAgASYAgEBsAIAAAwAAAAAAAAAAQAAAAAA" + + "AAABAAAADQAAAHAAAAACAAAABwAAAKQAAAADAAAAAgAAAMAAAAAFAAAABAAAANgAAAAGAAAAAQAA" + + "APgAAAABIAAAAgAAABgBAAABEAAAAgAAAFQBAAACIAAADQAAAGIBAAADIAAAAgAAABECAAAAIAAA" + + "AQAAABwCAAAAEAAAAQAAACwCAAA="); + + public static void doTest(Transform2 t) { + t.sayHi("ReorderInterface"); + try { + Main.doCommonClassRedefinition(Transform2.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("ReorderInterface"); + } +} diff --git a/test/921-hello-failure/src/Transform.java b/test/921-hello-failure/src/Transform.java new file mode 100644 index 0000000000..ee444f0606 --- /dev/null +++ b/test/921-hello-failure/src/Transform.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. + */ + +class Transform { + public void sayHi(String name) { + System.out.println("hello - " + name); + } +} diff --git a/test/921-hello-failure/src/Transform2.java b/test/921-hello-failure/src/Transform2.java new file mode 100644 index 0000000000..9d949f323f --- /dev/null +++ b/test/921-hello-failure/src/Transform2.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. + */ + +class Transform2 implements Iface1, Iface2 { + public void sayHi(String name) { + System.out.println("hello2 - " + name); + } +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index dd755b1504..c48f57350d 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -293,6 +293,7 @@ TEST_ART_BROKEN_TARGET_TESTS += \ 918-fields \ 919-obsolete-fields \ 920-objects \ + 921-hello-failure \ 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 ebf1e4621c..01b6c989cc 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -17,6 +17,7 @@ #include "ti-agent/common_helper.h" #include <stdio.h> +#include <sstream> #include "art_method.h" #include "jni.h" @@ -41,8 +42,24 @@ void SetAllCapabilities(jvmtiEnv* env) { namespace common_redefine { +static void throwRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, jclass target, jvmtiError res) { + std::stringstream err; + char* signature = nullptr; + char* generic = nullptr; + jvmti->GetClassSignature(target, &signature, &generic); + char* error = nullptr; + jvmti->GetErrorName(res, &error); + err << "Failed to redefine class <" << signature << "> due to " << error; + std::string message = err.str(); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature)); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic)); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(error)); + env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); +} + using RedefineDirectFunction = jvmtiError (*)(jvmtiEnv*, jclass, jint, const unsigned char*); -static void DoClassTransformation(jvmtiEnv* jvmti_env, JNIEnv* env, +static void DoClassTransformation(jvmtiEnv* jvmti_env, + JNIEnv* env, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { @@ -63,7 +80,7 @@ static void DoClassTransformation(jvmtiEnv* jvmti_env, JNIEnv* env, res = f(jvmti_env, target, len, redef_bytes); } if (res != JVMTI_ERROR_NONE) { - printf("Redefinition failed!"); + throwRedefinitionError(jvmti_env, env, target, res); } } diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 2f4af9f42d..d7579ca022 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -75,6 +75,7 @@ AgentLib agents[] = { { "918-fields", Test918Fields::OnLoad, nullptr }, { "919-obsolete-fields", common_redefine::OnLoad, nullptr }, { "920-objects", Test920Objects::OnLoad, nullptr }, + { "921-hello-failure", common_redefine::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |