diff options
| -rw-r--r-- | runtime/openjdkjvmti/ti_redefine.cc | 62 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_redefine.h | 7 | ||||
| -rw-r--r-- | test/921-hello-failure/expected.txt | 3 | ||||
| -rw-r--r-- | test/921-hello-failure/src/Main.java | 1 | ||||
| -rw-r--r-- | test/921-hello-failure/src/Verification.java | 82 |
5 files changed, 144 insertions, 11 deletions
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 058b93a042..59cb8763de 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -56,6 +56,8 @@ #include "ScopedLocalRef.h" #include "ti_class_loader.h" #include "transform.h" +#include "verifier/method_verifier.h" +#include "verifier/verifier_log_mode.h" namespace openjdkjvmti { @@ -703,7 +705,6 @@ bool Redefiner::ClassRedefinition::CheckClass() { } } LOG(WARNING) << "No verification is done on annotations of redefined classes."; - LOG(WARNING) << "Bytecodes of redefinitions are not verified."; return true; } @@ -766,26 +767,28 @@ class RedefinitionDataHolder { } // TODO Maybe make an iterable view type to simplify using this. - art::mirror::ClassLoader* GetSourceClassLoader(jint klass_index) + art::mirror::ClassLoader* GetSourceClassLoader(jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) { return art::down_cast<art::mirror::ClassLoader*>(GetSlot(klass_index, kSlotSourceClassLoader)); } - art::mirror::Object* GetJavaDexFile(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::mirror::Object* GetJavaDexFile(jint klass_index) const + REQUIRES_SHARED(art::Locks::mutator_lock_) { return GetSlot(klass_index, kSlotJavaDexFile); } - art::mirror::LongArray* GetNewDexFileCookie(jint klass_index) + art::mirror::LongArray* GetNewDexFileCookie(jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) { return art::down_cast<art::mirror::LongArray*>(GetSlot(klass_index, kSlotNewDexFileCookie)); } - art::mirror::DexCache* GetNewDexCache(jint klass_index) + art::mirror::DexCache* GetNewDexCache(jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) { return art::down_cast<art::mirror::DexCache*>(GetSlot(klass_index, kSlotNewDexCache)); } - art::mirror::Class* GetMirrorClass(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::mirror::Class* GetMirrorClass(jint klass_index) const + REQUIRES_SHARED(art::Locks::mutator_lock_) { return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass)); } - art::mirror::ByteArray* GetOriginalDexFileBytes(jint klass_index) + art::mirror::ByteArray* GetOriginalDexFileBytes(jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) { return art::down_cast<art::mirror::ByteArray*>(GetSlot(klass_index, kSlotOrigDexFile)); } @@ -815,15 +818,15 @@ class RedefinitionDataHolder { SetSlot(klass_index, kSlotOrigDexFile, bytes); } - int32_t Length() REQUIRES_SHARED(art::Locks::mutator_lock_) { + int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) { return arr_->GetLength() / kNumSlots; } private: - art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_; + mutable art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_; art::mirror::Object* GetSlot(jint klass_index, - DataSlot slot) REQUIRES_SHARED(art::Locks::mutator_lock_) { + DataSlot slot) const REQUIRES_SHARED(art::Locks::mutator_lock_) { DCHECK_LT(klass_index, Length()); return arr_->Get((kNumSlots * klass_index) + slot); } @@ -839,6 +842,31 @@ class RedefinitionDataHolder { DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder); }; +// TODO Stash and update soft failure state +bool Redefiner::ClassRedefinition::CheckVerification(int32_t klass_index, + const RedefinitionDataHolder& holder) { + DCHECK_EQ(dex_file_->NumClassDefs(), 1u); + art::StackHandleScope<2> hs(driver_->self_); + std::string error; + // TODO Make verification log level lower + art::verifier::MethodVerifier::FailureKind failure = + art::verifier::MethodVerifier::VerifyClass(driver_->self_, + dex_file_.get(), + hs.NewHandle(holder.GetNewDexCache(klass_index)), + hs.NewHandle(GetClassLoader()), + dex_file_->GetClassDef(0), /*class_def*/ + nullptr, /*compiler_callbacks*/ + false, /*allow_soft_failures*/ + /*log_level*/ + art::verifier::HardFailLogMode::kLogWarning, + &error); + bool passes = failure == art::verifier::MethodVerifier::kNoFailure; + if (!passes) { + RecordFailure(ERR(FAILS_VERIFICATION), "Failed to verify class. Error was: " + error); + } + return passes; +} + // Looks through the previously allocated cookies to see if we need to update them with another new // dexfile. This is so that even if multiple classes with the same classloader are redefined at // once they are all added to the classloader. @@ -978,6 +1006,17 @@ void Redefiner::ReleaseAllDexFiles() { } } +bool Redefiner::CheckAllClassesAreVerified(const RedefinitionDataHolder& holder) { + int32_t cnt = 0; + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + if (!redef.CheckVerification(cnt, holder)) { + return false; + } + cnt++; + } + return true; +} + jvmtiError Redefiner::Run() { art::StackHandleScope<1> hs(self_); // Allocate an array to hold onto all java temporary objects associated with this redefinition. @@ -997,7 +1036,8 @@ jvmtiError Redefiner::Run() { // try loop. if (!CheckAllRedefinitionAreValid() || !EnsureAllClassAllocationsFinished() || - !FinishAllRemainingAllocations(holder)) { + !FinishAllRemainingAllocations(holder) || + !CheckAllClassesAreVerified(holder)) { // TODO Null out the ClassExt fields we allocated (if possible, might be racing with another // redefineclass call which made it even bigger. Leak shouldn't be huge (2x array of size // declared_methods_.length) but would be good to get rid of. All other allocations should be diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 3209abbe64..421d22ef4c 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -165,6 +165,11 @@ class Redefiner { // data has not been modified in an incompatible manner. bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_); + // Checks that the contained class can be successfully verified. + bool CheckVerification(int32_t klass_index, + const RedefinitionDataHolder& holder) + REQUIRES_SHARED(art::Locks::mutator_lock_); + // Preallocates all needed allocations in klass so that we can pause execution safely. // TODO We should be able to free the arrays if they end up not being used. Investigate doing // this in the future. For now we will just take the memory hit. @@ -239,6 +244,8 @@ class Redefiner { jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_); bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_); + bool CheckAllClassesAreVerified(const RedefinitionDataHolder& holder) + REQUIRES_SHARED(art::Locks::mutator_lock_); bool EnsureAllClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_); bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder) REQUIRES_SHARED(art::Locks::mutator_lock_); diff --git a/test/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt index e9b6a20cd6..a5dc10d59c 100644 --- a/test/921-hello-failure/expected.txt +++ b/test/921-hello-failure/expected.txt @@ -1,3 +1,6 @@ +hello - Verification +Transformation error : java.lang.Exception(Failed to redefine class <LTransform;> due to JVMTI_ERROR_FAILS_VERIFICATION) +hello - Verification hello - NewName Transformation error : java.lang.Exception(Failed to redefine class <LTransform;> due to JVMTI_ERROR_NAMES_DONT_MATCH) hello - NewName diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java index 61d69e7396..5bbe2b5479 100644 --- a/test/921-hello-failure/src/Main.java +++ b/test/921-hello-failure/src/Main.java @@ -18,6 +18,7 @@ import java.util.ArrayList; public class Main { public static void main(String[] args) { + Verification.doTest(new Transform()); NewName.doTest(new Transform()); DifferentAccess.doTest(new Transform()); NewInterface.doTest(new Transform2()); diff --git a/test/921-hello-failure/src/Verification.java b/test/921-hello-failure/src/Verification.java new file mode 100644 index 0000000000..242b5d2b44 --- /dev/null +++ b/test/921-hello-failure/src/Verification.java @@ -0,0 +1,82 @@ +/* + * 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 Verification { + // Jasmin program: + // + // .source Transform.java + // .class Transform + // .super java/lang/Object + // .method <init>()V + // .limit stack 1 + // .limit locals 1 + // aload_0 + // invokespecial java/lang/Object/<init>()V + // return + // .end method + // .method sayHi(Ljava/lang/String;)V + // .limit stack 1 + // .limit locals 2 + // aload_1 + // areturn + // .end method + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgADAC0ADgoADQAHBwAIAQAQamF2YS9sYW5nL09iamVjdAEAClNvdXJjZUZpbGUBAAY8aW5p" + + "dD4BAAVzYXlIaQwABQAKAQAJVHJhbnNmb3JtAQAEQ29kZQEAAygpVgEADlRyYW5zZm9ybS5qYXZh" + + "AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWBwADACAAAgANAAAAAAACAAAABQAKAAEACQAAABEAAQAB" + + "AAAABSq3AAGxAAAAAAABAAYADAABAAkAAAAOAAEAAgAAAAIrsAAAAAAAAQAEAAAAAgAL"); + + // Smali program: + // + // .class LTransform; + // .super Ljava/lang/Object; + // .source "Transform.java" + // # direct methods + // .method constructor <init>()V + // .registers 1 + // invoke-direct {p0}, Ljava/lang/Object;-><init>()V + // return-void + // .end method + // # virtual methods + // .method public sayHi(Ljava/lang/String;)V + // .registers 2 + // return-object p1 + // .end method + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQClOAc4ZDMXaHMezhYcqZxcjUeVCWRYUkooAgAAcAAAAHhWNBIAAAAAAAAAAJQBAAAI" + + "AAAAcAAAAAQAAACQAAAAAgAAAKAAAAAAAAAAAAAAAAMAAAC4AAAAAQAAANAAAAA4AQAA8AAAAPAA" + + "AAD4AAAABQEAABkBAAAtAQAAPQEAAEABAABEAQAAAQAAAAIAAAADAAAABQAAAAUAAAADAAAAAAAA" + + "AAYAAAADAAAATAEAAAAAAAAAAAAAAAABAAcAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAA" + + "AAAAAIYBAAAAAAAABjxpbml0PgALTFRyYW5zZm9ybTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGph" + + "dmEvbGFuZy9TdHJpbmc7AA5UcmFuc2Zvcm0uamF2YQABVgACVkwABXNheUhpAAABAAAAAgAAAAAA" + + "AAAAAAAAAQABAAEAAAAAAAAABAAAAHAQAgAAAA4AAgACAAAAAAAAAAAAAQAAABEBAAABAQCAgATc" + + "AgEB9AIMAAAAAAAAAAEAAAAAAAAAAQAAAAgAAABwAAAAAgAAAAQAAACQAAAAAwAAAAIAAACgAAAA" + + "BQAAAAMAAAC4AAAABgAAAAEAAADQAAAAAiAAAAgAAADwAAAAARAAAAEAAABMAQAAAxAAAAIAAABU" + + "AQAAASAAAAIAAABcAQAAACAAAAEAAACGAQAAABAAAAEAAACUAQAA"); + + public static void doTest(Transform t) { + t.sayHi("Verification"); + try { + Main.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("Verification"); + } +} |