Merge "ART: More valgrind suppressions"
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index b3b1903..4109345 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -2341,16 +2341,6 @@
 void ImageWriter::CopyAndFixupMethod(ArtMethod* orig,
                                      ArtMethod* copy,
                                      const ImageInfo& image_info) {
-  if (orig->IsAbstract()) {
-    // Ignore the single-implementation info for abstract method.
-    // Do this on orig instead of copy, otherwise there is a crash due to methods
-    // are copied before classes.
-    // TODO: handle fixup of single-implementation method for abstract method.
-    orig->SetHasSingleImplementation(false);
-    orig->SetSingleImplementation(
-        nullptr, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
-  }
-
   memcpy(copy, orig, ArtMethod::Size(target_ptr_size_));
 
   copy->SetDeclaringClass(GetImageAddress(orig->GetDeclaringClassUnchecked()));
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 7772e8f..5d40f75 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -304,8 +304,7 @@
     // We do not support HDeoptimize in OSR methods.
     return nullptr;
   }
-  PointerSize pointer_size = caller_compilation_unit_.GetClassLinker()->GetImagePointerSize();
-  return resolved_method->GetSingleImplementation(pointer_size);
+  return resolved_method->GetSingleImplementation();
 }
 
 bool HInliner::TryInline(HInvoke* invoke_instruction) {
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 92f3727..d7d39af 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -55,13 +55,15 @@
 extern "C" void art_quick_invoke_static_stub(ArtMethod*, uint32_t*, uint32_t, Thread*, JValue*,
                                              const char*);
 
-ArtMethod* ArtMethod::GetSingleImplementation(PointerSize pointer_size) {
+ArtMethod* ArtMethod::GetSingleImplementation() {
   DCHECK(!IsNative());
   if (!IsAbstract()) {
     // A non-abstract's single implementation is itself.
     return this;
   }
-  return reinterpret_cast<ArtMethod*>(GetDataPtrSize(pointer_size));
+  // TODO: add single-implementation logic for abstract method by storing it
+  // in ptr_sized_fields_.
+  return nullptr;
 }
 
 ArtMethod* ArtMethod::FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa,
diff --git a/runtime/art_method.h b/runtime/art_method.h
index a33111a..17f343d 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -456,7 +456,7 @@
     }
   }
 
-  ArtMethod* GetSingleImplementation(PointerSize pointer_size)
+  ArtMethod* GetSingleImplementation()
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE void SetSingleImplementation(ArtMethod* method, PointerSize pointer_size) {
@@ -684,8 +684,7 @@
     ArtMethod** dex_cache_resolved_methods_;
 
     // Pointer to JNI function registered to this method, or a function to resolve the JNI function,
-    // or the profiling data for non-native methods, or an ImtConflictTable, or the
-    // single-implementation of an abstract method.
+    // or the profiling data for non-native methods, or an ImtConflictTable.
     void* data_;
 
     // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
diff --git a/runtime/cha.cc b/runtime/cha.cc
index a1ad585..d94b091 100644
--- a/runtime/cha.cc
+++ b/runtime/cha.cc
@@ -185,8 +185,7 @@
 };
 
 void ClassHierarchyAnalysis::VerifyNonSingleImplementation(mirror::Class* verify_class,
-                                                           uint16_t verify_index,
-                                                           ArtMethod* excluded_method) {
+                                                           uint16_t verify_index) {
   // Grab cha_lock_ to make sure all single-implementation updates are seen.
   PointerSize image_pointer_size =
       Runtime::Current()->GetClassLinker()->GetImagePointerSize();
@@ -196,11 +195,9 @@
       return;
     }
     ArtMethod* verify_method = verify_class->GetVTableEntry(verify_index, image_pointer_size);
-    if (verify_method != excluded_method) {
-      DCHECK(!verify_method->HasSingleImplementation())
-          << "class: " << verify_class->PrettyClass()
-          << " verify_method: " << verify_method->PrettyMethod(true);
-    }
+    DCHECK(!verify_method->HasSingleImplementation())
+        << "class: " << verify_class->PrettyClass()
+        << " verify_method: " << verify_method->PrettyMethod(true);
     verify_class = verify_class->GetSuperClass();
   }
 }
@@ -209,152 +206,41 @@
     Handle<mirror::Class> klass,
     ArtMethod* virtual_method,
     ArtMethod* method_in_super,
-    std::unordered_set<ArtMethod*>& invalidated_single_impl_methods,
-    PointerSize pointer_size) {
+    std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) {
   // TODO: if klass is not instantiable, virtual_method isn't invocable yet so
   // even if it overrides, it doesn't invalidate single-implementation
   // assumption.
 
-  DCHECK((virtual_method != method_in_super) || virtual_method->IsAbstract());
+  DCHECK_NE(virtual_method, method_in_super);
   DCHECK(method_in_super->GetDeclaringClass()->IsResolved()) << "class isn't resolved";
   // If virtual_method doesn't come from a default interface method, it should
   // be supplied by klass.
-  DCHECK(virtual_method == method_in_super ||
-         virtual_method->IsCopied() ||
+  DCHECK(virtual_method->IsCopied() ||
          virtual_method->GetDeclaringClass() == klass.Get());
 
-  // To make updating single-implementation flags simple, we always maintain the following
-  // invariant:
-  // Say all virtual methods in the same vtable slot, starting from the bottom child class
-  // to super classes, is a sequence of unique methods m3, m2, m1, ... (after removing duplicate
-  // methods for inherited methods).
-  // For example for the following class hierarchy,
-  //   class A { void m() { ... } }
-  //   class B extends A { void m() { ... } }
-  //   class C extends B {}
-  //   class D extends C { void m() { ... } }
-  // the sequence is D.m(), B.m(), A.m().
-  // The single-implementation status for that sequence of methods begin with one or two true's,
-  // then become all falses. The only case where two true's are possible is for one abstract
-  // method m and one non-abstract method mImpl that overrides method m.
-  // With the invariant, when linking in a new class, we only need to at most update one or
-  // two methods in the sequence for their single-implementation status, in order to maintain
-  // the invariant.
-
+  // A new virtual_method should set method_in_super to
+  // non-single-implementation (if not set already).
+  // We don't grab cha_lock_. Single-implementation flag won't be set to true
+  // again once it's set to false.
   if (!method_in_super->HasSingleImplementation()) {
     // method_in_super already has multiple implementations. All methods in the
     // same vtable slots in its super classes should have
     // non-single-implementation already.
     if (kIsDebugBuild) {
       VerifyNonSingleImplementation(klass->GetSuperClass()->GetSuperClass(),
-                                    method_in_super->GetMethodIndex(),
-                                    nullptr /* excluded_method */);
+                                    method_in_super->GetMethodIndex());
     }
     return;
   }
 
   // Native methods don't have single-implementation flag set.
   DCHECK(!method_in_super->IsNative());
-
-  uint16_t method_index = method_in_super->GetMethodIndex();
-  if (method_in_super->IsAbstract()) {
-    if (kIsDebugBuild) {
-      // An abstract method should have made all methods in the same vtable
-      // slot above it in the class hierarchy having non-single-implementation.
-      mirror::Class* super_super = klass->GetSuperClass()->GetSuperClass();
-      VerifyNonSingleImplementation(super_super,
-                                    method_index,
-                                    method_in_super);
-    }
-
-    if (virtual_method->IsAbstract()) {
-      // SUPER: abstract, VIRTUAL: abstract.
-      if (method_in_super == virtual_method) {
-        DCHECK(klass->IsInstantiable());
-        // An instantiable subclass hasn't provided a concrete implementation of
-        // the abstract method. Invoking method_in_super may throw AbstractMethodError.
-        // This is an uncommon case, so we simply treat method_in_super as not
-        // having single-implementation.
-        invalidated_single_impl_methods.insert(method_in_super);
-        return;
-      } else {
-        // One abstract method overrides another abstract method. This is an uncommon
-        // case. We simply treat method_in_super as not having single-implementation.
-        invalidated_single_impl_methods.insert(method_in_super);
-        return;
-      }
-    } else {
-      // SUPER: abstract, VIRTUAL: non-abstract.
-      // A non-abstract method overrides an abstract method.
-      if (method_in_super->GetSingleImplementation(pointer_size) == nullptr) {
-        // Abstract method_in_super gets its first implementation. Keep it as having
-        // single-implementation and record that single implementation.
-        DCHECK(method_in_super->HasSingleImplementation());
-        method_in_super->SetSingleImplementation(virtual_method, pointer_size);
-        return;
-      } else {
-        // Abstract method_in_super already got one implementation.
-        // Invalidate method_in_super's single-implementation status.
-        invalidated_single_impl_methods.insert(method_in_super);
-        return;
-      }
-    }
-  } else {
-    if (virtual_method->IsAbstract()) {
-      // SUPER: non-abstract, VIRTUAL: abstract.
-      // An abstract method overrides a non-abstract method. This is an uncommon
-      // case, we simply treat both methods as not having single-implementation.
-      invalidated_single_impl_methods.insert(virtual_method);
-      // Fall-through to handle invalidating method_in_super of its
-      // single-implementation status.
-    }
-
-    // SUPER: non-abstract, VIRTUAL: non-abstract/abstract(fall-through from previous if).
-    // Invalidate method_in_super's single-implementation status.
-    invalidated_single_impl_methods.insert(method_in_super);
-
-    // method_in_super might be the single-implementation of another abstract method,
-    // which should be also invalidated of its single-implementation status.
-    mirror::Class* super_super = klass->GetSuperClass()->GetSuperClass();
-    while (super_super != nullptr &&
-           method_index < super_super->GetVTableLength()) {
-      ArtMethod* method_in_super_super = super_super->GetVTableEntry(method_index, pointer_size);
-      if (method_in_super_super != method_in_super) {
-        if (method_in_super_super->IsAbstract()) {
-          if (method_in_super_super->HasSingleImplementation()) {
-            // Invalidate method_in_super's single-implementation status.
-            invalidated_single_impl_methods.insert(method_in_super_super);
-            // No need to further traverse up the class hierarchy since if there
-            // are cases that one abstract method overrides another method, we
-            // should have made that method having non-single-implementation already.
-          } else {
-            // method_in_super_super is already non-single-implementation.
-            // No need to further traverse up the class hierarchy.
-          }
-        } else {
-          DCHECK(!method_in_super_super->HasSingleImplementation());
-          // No need to further traverse up the class hierarchy since two non-abstract
-          // methods (method_in_super and method_in_super_super) should have set all
-          // other methods (abstract or not) in the vtable slot to be non-single-implementation.
-        }
-
-        if (kIsDebugBuild) {
-          VerifyNonSingleImplementation(super_super->GetSuperClass(),
-                                        method_index,
-                                        method_in_super_super);
-        }
-        // No need to go any further.
-        return;
-      } else {
-        super_super = super_super->GetSuperClass();
-      }
-    }
-  }
+  // Invalidate method_in_super's single-implementation status.
+  invalidated_single_impl_methods.insert(method_in_super);
 }
 
 void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> klass,
-                                                          ArtMethod* method,
-                                                          PointerSize pointer_size) {
+                                                          ArtMethod* method) {
   DCHECK(method->IsCopied() || method->GetDeclaringClass() == klass.Get());
   if (klass->IsFinal() || method->IsFinal()) {
     // Final classes or methods do not need CHA for devirtualization.
@@ -367,21 +253,16 @@
     // cannot be inlined. It's not worthwhile to devirtualize the
     // call which can add a deoptimization point.
     DCHECK(!method->HasSingleImplementation());
-  } else if (method->IsAbstract()) {
-    if (method->GetDeclaringClass()->IsInstantiable()) {
-      // Rare case, but we do accept it (such as 800-smali/smali/b_26143249.smali).
-      // Do not attempt to devirtualize it.
-      method->SetHasSingleImplementation(false);
-    } else {
-      // Abstract method starts with single-implementation flag set and null
-      // implementation method.
-      method->SetHasSingleImplementation(true);
-      DCHECK(method->GetSingleImplementation(pointer_size) == nullptr);
-    }
   } else {
     method->SetHasSingleImplementation(true);
-    // Single implementation of non-abstract method is itself.
-    DCHECK_EQ(method->GetSingleImplementation(pointer_size), method);
+    if (method->IsAbstract()) {
+      // There is no real implementation yet.
+      // TODO: implement single-implementation logic for abstract methods.
+      DCHECK(method->GetSingleImplementation() == nullptr);
+    } else {
+      // Single implementation of non-abstract method is itself.
+      DCHECK_EQ(method->GetSingleImplementation(), method);
+    }
   }
 }
 
@@ -405,29 +286,19 @@
     ArtMethod* method_in_super = super_class->GetVTableEntry(i, image_pointer_size);
     if (method == method_in_super) {
       // vtable slot entry is inherited from super class.
-      if (method->IsAbstract() && klass->IsInstantiable()) {
-        // An instantiable class that inherits an abstract method is treated as
-        // supplying an implementation that throws AbstractMethodError.
-        CheckSingleImplementationInfo(klass,
-                                      method,
-                                      method_in_super,
-                                      invalidated_single_impl_methods,
-                                      image_pointer_size);
-      }
       continue;
     }
-    InitSingleImplementationFlag(klass, method, image_pointer_size);
+    InitSingleImplementationFlag(klass, method);
     CheckSingleImplementationInfo(klass,
                                   method,
                                   method_in_super,
-                                  invalidated_single_impl_methods,
-                                  image_pointer_size);
+                                  invalidated_single_impl_methods);
   }
 
   // For new virtual methods that don't override.
   for (int32_t i = super_class->GetVTableLength(); i < klass->GetVTableLength(); ++i) {
     ArtMethod* method = klass->GetVTableEntry(i, image_pointer_size);
-    InitSingleImplementationFlag(klass, method, image_pointer_size);
+    InitSingleImplementationFlag(klass, method);
   }
 
   Runtime* const runtime = Runtime::Current();
diff --git a/runtime/cha.h b/runtime/cha.h
index a56a752..ada5c89 100644
--- a/runtime/cha.h
+++ b/runtime/cha.h
@@ -112,9 +112,7 @@
   void UpdateAfterLoadingOf(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
-  void InitSingleImplementationFlag(Handle<mirror::Class> klass,
-                                    ArtMethod* method,
-                                    PointerSize pointer_size)
+  void InitSingleImplementationFlag(Handle<mirror::Class> klass, ArtMethod* method)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // `virtual_method` in `klass` overrides `method_in_super`.
@@ -125,16 +123,12 @@
       Handle<mirror::Class> klass,
       ArtMethod* virtual_method,
       ArtMethod* method_in_super,
-      std::unordered_set<ArtMethod*>& invalidated_single_impl_methods,
-      PointerSize pointer_size)
+      std::unordered_set<ArtMethod*>& invalidated_single_impl_methods)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // For all methods in vtable slot at `verify_index` of `verify_class` and its
-  // superclasses, single-implementation status should be false, except if the
-  // method is `excluded_method`.
-  void VerifyNonSingleImplementation(mirror::Class* verify_class,
-                                     uint16_t verify_index,
-                                     ArtMethod* excluded_method)
+  // Verify all methods in the same vtable slot from verify_class and its supers
+  // don't have single-implementation.
+  void VerifyNonSingleImplementation(mirror::Class* verify_class, uint16_t verify_index)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // A map that maps a method to a set of compiled code that assumes that method has a
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 0341c64..d98daa5 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -612,7 +612,7 @@
   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 7c6a710..efd949e 100644
--- a/runtime/mirror/class_ext.cc
+++ b/runtime/mirror/class_ext.cc
@@ -113,6 +113,11 @@
   }
 }
 
+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 9104631..ad8a61b 100644
--- a/runtime/mirror/class_ext.h
+++ b/runtime/mirror/class_ext.h
@@ -61,6 +61,12 @@
     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 @@
 
   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 acdd0d3..a731c17 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -21,6 +21,7 @@
            "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 1c84d4d..256c3a6 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 @@
   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 0000000..2c2a79b
--- /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 0000000..dbe5da2
--- /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 2db8a40..34efc50 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 @@
   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 @@
     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 @@
   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 @@
     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 @@
   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 @@
     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 @@
     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 @@
       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 @@
   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::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 @@
     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 @@
 }
 
 // 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 @@
   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 f8d51ad..29a7e1f 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 @@
     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 @@
         : 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 @@
     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 @@
         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 @@
     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 2809cb6..af4fb71 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 @@
   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 0ff2bd1..65f2ae1 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/616-cha-abstract/expected.txt b/test/616-cha-abstract/expected.txt
deleted file mode 100644
index 6a5618e..0000000
--- a/test/616-cha-abstract/expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-JNI_OnLoad called
diff --git a/test/616-cha-abstract/info.txt b/test/616-cha-abstract/info.txt
deleted file mode 100644
index 4f7e013..0000000
--- a/test/616-cha-abstract/info.txt
+++ /dev/null
@@ -1 +0,0 @@
-Test for Class Hierarchy Analysis (CHA) on abstract method.
diff --git a/test/616-cha-abstract/src/Main.java b/test/616-cha-abstract/src/Main.java
deleted file mode 100644
index e1d7db1..0000000
--- a/test/616-cha-abstract/src/Main.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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.
- */
-
-abstract class Base {
-  abstract void foo(int i);
-
-  void printError(String msg) {
-    System.out.println(msg);
-  }
-}
-
-class Main1 extends Base {
-  void foo(int i) {
-    if (i != 1) {
-      printError("error1");
-    }
-  }
-}
-
-class Main2 extends Main1 {
-  void foo(int i) {
-    if (i != 2) {
-      printError("error2");
-    }
-  }
-}
-
-public class Main {
-  static Main1 sMain1;
-  static Main1 sMain2;
-
-  static boolean sIsOptimizing = true;
-  static boolean sHasJIT = true;
-  static volatile boolean sOtherThreadStarted;
-
-  private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
-    if (hasSingleImplementation(clazz, method_name) != b) {
-      System.out.println(clazz + "." + method_name +
-          " doesn't have single implementation value of " + b);
-    }
-  }
-
-  // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
-  // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
-  // After Dummy.createMain2() which links in Main2, live testOverride() on stack
-  // should be deoptimized.
-  static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
-    if (setHasJIT) {
-      if (isInterpreted()) {
-        sHasJIT = false;
-      }
-      return;
-    }
-
-    if (createMain2 && (sIsOptimizing || sHasJIT)) {
-      assertIsManaged();
-    }
-
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-
-    if (createMain2) {
-      // Wait for the other thread to start.
-      while (!sOtherThreadStarted);
-      // Create an Main2 instance and assign it to sMain2.
-      // sMain1 is kept the same.
-      sMain2 = Dummy.createMain2();
-      // Wake up the other thread.
-      synchronized(Main.class) {
-        Main.class.notify();
-      }
-    } else if (wait) {
-      // This is the other thread.
-      synchronized(Main.class) {
-        sOtherThreadStarted = true;
-        // Wait for Main2 to be linked and deoptimization is triggered.
-        try {
-          Main.class.wait();
-        } catch (Exception e) {
-        }
-      }
-    }
-
-    // There should be a deoptimization here right after Main2 is linked by
-    // calling Dummy.createMain2(), even though sMain1 didn't change.
-    // The behavior here would be different if inline-cache is used, which
-    // doesn't deoptimize since sMain1 still hits the type cache.
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-    if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
-      // This method should be deoptimized right after Main2 is created.
-      assertIsInterpreted();
-    }
-
-    if (sMain2 != null) {
-      sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
-    }
-  }
-
-  // Test scenarios under which CHA-based devirtualization happens,
-  // and class loading that overrides a method can invalidate compiled code.
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
-
-    if (isInterpreted()) {
-      sIsOptimizing = false;
-    }
-
-    // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
-    sMain1 = new Main1();
-
-    ensureJitCompiled(Main.class, "testOverride");
-    testOverride(false, false, true);
-
-    if (sHasJIT && !sIsOptimizing) {
-      assertSingleImplementation(Base.class, "foo", true);
-      assertSingleImplementation(Main1.class, "foo", true);
-    } else {
-      // Main2 is verified ahead-of-time so it's linked in already.
-    }
-
-    // Create another thread that also calls sMain1.foo().
-    // Try to test suspend and deopt another thread.
-    new Thread() {
-      public void run() {
-        testOverride(false, true, false);
-      }
-    }.start();
-
-    // This will create Main2 instance in the middle of testOverride().
-    testOverride(true, false, false);
-    assertSingleImplementation(Base.class, "foo", false);
-    assertSingleImplementation(Main1.class, "foo", false);
-  }
-
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
-  private static native void assertIsInterpreted();
-  private static native void assertIsManaged();
-  private static native boolean isInterpreted();
-  private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
-}
-
-// Put createMain2() in another class to avoid class loading due to verifier.
-class Dummy {
-  static Main1 createMain2() {
-    return new Main2();
-  }
-}
diff --git a/test/616-cha-abstract/run b/test/932-transform-saves/build
old mode 100644
new mode 100755
similarity index 70%
rename from test/616-cha-abstract/run
rename to test/932-transform-saves/build
index d8b4f0d..898e2e5
--- a/test/616-cha-abstract/run
+++ b/test/932-transform-saves/build
@@ -1,12 +1,12 @@
 #!/bin/bash
 #
-# Copyright (C) 2017 The Android Open Source Project
+# 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
+#      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,
@@ -14,5 +14,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
+./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 0000000..5097771
--- /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 0000000..875a5f6
--- /dev/null
+++ b/test/932-transform-saves/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/616-cha-abstract/run b/test/932-transform-saves/run
old mode 100644
new mode 100755
similarity index 69%
copy from test/616-cha-abstract/run
copy to test/932-transform-saves/run
index d8b4f0d..4379349
--- a/test/616-cha-abstract/run
+++ b/test/932-transform-saves/run
@@ -1,12 +1,12 @@
 #!/bin/bash
 #
-# Copyright (C) 2017 The Android Open Source Project
+# 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
+#      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,
@@ -14,5 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
+./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 0000000..d98ba6d
--- /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 0000000..8e8af35
--- /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 c8e2185..639996e 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -311,6 +311,7 @@
   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 8799c91..4bceef5 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -253,11 +253,12 @@
                                                     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 1b11442..f4ce4c3 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -67,6 +67,7 @@
   { "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) {
diff --git a/tools/dexfuzz/src/dexfuzz/Options.java b/tools/dexfuzz/src/dexfuzz/Options.java
index 99e03e8..af8a05c 100644
--- a/tools/dexfuzz/src/dexfuzz/Options.java
+++ b/tools/dexfuzz/src/dexfuzz/Options.java
@@ -50,6 +50,7 @@
   public static String deviceName = "";
   public static boolean usingSpecificDevice = false;
   public static int repeat = 1;
+  public static int divergenceRetry = 10;
   public static String executeDirectory = "/data/art-test";
   public static String androidRoot = "";
   public static String dumpMutationsFile = "mutations.dump";
@@ -118,6 +119,8 @@
     Log.always("    --repeat=<n>         : Fuzz N programs, executing each one.");
     Log.always("    --short-timeouts     : Shorten timeouts (faster; use if");
     Log.always("                           you want to focus on output divergences)");
+    Log.always("    --divergence-retry=<n> : Number of retries when checking if test is");
+    Log.always("                           self-divergent. (Default: 10)");
     Log.always("  --seed=<seed>          : RNG seed to use");
     Log.always("  --method-mutations=<n> : Maximum number of mutations to perform on each method.");
     Log.always("                           (Default: 3)");
@@ -239,6 +242,8 @@
       maxMethods = Integer.parseInt(value);
     } else if (key.equals("repeat")) {
       repeat = Integer.parseInt(value);
+    } else if (key.equals("divergence-retry")) {
+      divergenceRetry = Integer.parseInt(value);
     } else if (key.equals("log")) {
       Log.setLoggingLevel(LogTag.valueOf(value.toUpperCase()));
     } else if (key.equals("likelihoods")) {
@@ -360,6 +365,10 @@
       Log.error("--repeat must be at least 1!");
       return false;
     }
+    if (divergenceRetry < 0) {
+      Log.error("--divergence-retry cannot be negative!");
+      return false;
+    }
     if (usingProvidedSeed && repeat > 1) {
       Log.error("Cannot use --repeat with --seed");
       return false;
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
index 1797d90..ccc426c 100644
--- a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
@@ -298,13 +298,13 @@
   }
 
   private boolean checkGoldenExecutorForSelfDivergence(String programName) {
-    // Run golden executor 5 times, make sure it always produces
+    // Run golden executor multiple times, make sure it always produces
     // the same output, otherwise report that it is self-divergent.
 
     // TODO: Instead, produce a list of acceptable outputs, and see if the divergent
     // outputs of the backends fall within this set of outputs.
     String seenOutput = null;
-    for (int i = 0; i < 5; i++) {
+    for (int i = 0; i < Options.divergenceRetry + 1; i++) {
       goldenExecutor.reset();
       goldenExecutor.execute(programName);
       String output = goldenExecutor.getResult().getFlattenedOutput();