Add AddToDexClassloader JVMTI extension functions

It is useful for some agents to be able to add new classes into an
already existing classloader. This could be used to, for example,
handle new lambdas added during 'edit-and-continue' debugging. This
extension should eliminate the need for agents to reach into
class-loader internals.

These functions are:

'com.android.art.classloader.add_to_dex_class_loader' which has a
signature of jvmtiError(jvmtiEnv* env, jobject classloader, const
char* segment) and will add the given 'segment' file to the
dalvik.system.BaseDexClassLoader 'loader'.

'com.android.art.classloader.add_to_dex_class_loader_in_memory' which
has a signature of jvmtiError(jvmtiEnv* env, jobject classloader,
const unsigned char* dex, jint dex_size) and will add the dexfile
buffer 'dex' to the given dalvik.system.BaseDexClassLoader.

ClassLoaders that do not extend dalvik.system.BaseDexClassLoader are
not supported.

Test: ./test.py --host
Bug: 132699522
Bug: 132914283

Change-Id: I3740af4b3b06b9fa64be8ad94238256b7a43536a
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index f12cb0a..08667c3 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -42,6 +42,7 @@
 #include "ti_heap.h"
 #include "ti_logging.h"
 #include "ti_monitor.h"
+#include "ti_search.h"
 
 #include "thread-inl.h"
 
@@ -327,6 +328,54 @@
     return error;
   }
 
+  // AddToDexClassLoader
+  error = add_extension(
+      reinterpret_cast<jvmtiExtensionFunction>(SearchUtil::AddToDexClassLoader),
+      "com.android.art.classloader.add_to_dex_class_loader",
+      "Adds a dexfile to a given dalvik.system.BaseDexClassLoader in a manner similar to"
+      " AddToSystemClassLoader.",
+      {
+        { "classloader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
+        { "segment", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false },
+      },
+      {
+         ERR(NULL_POINTER),
+         ERR(CLASS_LOADER_UNSUPPORTED),
+         ERR(ILLEGAL_ARGUMENT),
+         ERR(WRONG_PHASE),
+      });
+  if (error != ERR(NONE)) {
+    return error;
+  }
+
+  // AddToDexClassLoaderInMemory requires memfd_create which non-linux doesn't have. The code will
+  // still all link but since it will only ever return ERR(INTERNAL) we might as well not even
+  // advertise the extension.
+  // TODO Support non-linux in some way.
+#ifdef __linux__
+  // AddToDexClassLoaderInMemory
+  error = add_extension(
+      reinterpret_cast<jvmtiExtensionFunction>(SearchUtil::AddToDexClassLoaderInMemory),
+      "com.android.art.classloader.add_to_dex_class_loader_in_memory",
+      "Adds a dexfile buffer to a given dalvik.system.BaseDexClassLoader in a manner similar to"
+      " AddToSystemClassLoader. This may only be done during the LIVE phase. The buffer is copied"
+      " and the caller is responsible for deallocating it after this call.",
+      {
+        { "classloader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
+        { "dex_bytes", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
+        { "dex_bytes_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+      },
+      {
+         ERR(NULL_POINTER),
+         ERR(CLASS_LOADER_UNSUPPORTED),
+         ERR(ILLEGAL_ARGUMENT),
+         ERR(WRONG_PHASE),
+      });
+  if (error != ERR(NONE)) {
+    return error;
+  }
+#endif
+
   // Copy into output buffer.
 
   *extension_count_ptr = ext_vector.size();
diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
index 2187825..1eadf11 100644
--- a/openjdkjvmti/ti_search.cc
+++ b/openjdkjvmti/ti_search.cc
@@ -29,6 +29,9 @@
  * questions.
  */
 
+#include <sstream>
+#include <unistd.h>
+
 #include "ti_search.h"
 
 #include "jni.h"
@@ -37,6 +40,9 @@
 #include "art_jvmti.h"
 #include "base/enums.h"
 #include "base/macros.h"
+#include "base/memfd.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
 #include "class_linker.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/dex_file.h"
@@ -249,8 +255,121 @@
   return ERR(NONE);
 }
 
-jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED,
-                                                    const char* segment) {
+jvmtiError SearchUtil::AddToDexClassLoaderInMemory(jvmtiEnv* jvmti_env,
+                                                   jobject classloader,
+                                                   const char* dex_bytes,
+                                                   jint dex_bytes_length) {
+  if (jvmti_env == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (art::Thread::Current() == nullptr) {
+    return ERR(UNATTACHED_THREAD);
+  } else if (classloader == nullptr) {
+    return ERR(NULL_POINTER);
+  } else if (dex_bytes == nullptr) {
+    return ERR(NULL_POINTER);
+  } else if (dex_bytes_length <= 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked();
+
+  // TODO We really should try to support doing this during the ON_LOAD phase.
+  if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) {
+    JVMTI_LOG(INFO, jvmti_env) << "Cannot add buffers to classpath during ON_LOAD phase to "
+                               << "prevent file-descriptor leaking.";
+    return ERR(WRONG_PHASE);
+  }
+
+  // We have java APIs for adding files to the classpath, we might as well use them. It simplifies a
+  // lot of code as well.
+
+  // Create a memfd
+  art::File file(art::memfd_create("JVMTI InMemory Added dex file", 0), /*check-usage*/true);
+  if (file.Fd() < 0) {
+    char* reason = strerror(errno);
+    JVMTI_LOG(ERROR, jvmti_env) << "Unable to create memfd due to " << reason;
+    return ERR(INTERNAL);
+  }
+  // Fill it with the buffer.
+  if (!file.WriteFully(dex_bytes, dex_bytes_length) || file.Flush() != 0) {
+    JVMTI_LOG(ERROR, jvmti_env) << "Failed to write to memfd!";
+    return ERR(INTERNAL);
+  }
+  // Get the filename in procfs.
+  std::ostringstream oss;
+  oss << "/proc/self/fd/" << file.Fd();
+  std::string seg(oss.str());
+  // Use common code.
+
+  jvmtiError result = AddToDexClassLoader(jvmti_env, classloader, seg.c_str());
+  // We have either loaded the dex file and have a new MemMap pointing to the same pages or loading
+  // has failed and the memory isn't needed anymore. Either way we can close the memfd we created
+  // and return.
+  if (file.Close() != 0) {
+    JVMTI_LOG(WARNING, jvmti_env) << "Failed to close memfd!";
+  }
+  return result;
+}
+
+jvmtiError SearchUtil::AddToDexClassLoader(jvmtiEnv* jvmti_env,
+                                           jobject classloader,
+                                           const char* segment) {
+  if (jvmti_env == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (art::Thread::Current() == nullptr) {
+    return ERR(UNATTACHED_THREAD);
+  } else if (classloader == nullptr) {
+    return ERR(NULL_POINTER);
+  } else if (segment == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked();
+
+  // TODO We really should try to support doing this during the ON_LOAD phase.
+  if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) {
+    JVMTI_LOG(INFO, jvmti_env) << "Cannot add to classpath of arbitrary classloaders during "
+                               << "ON_LOAD phase.";
+    return ERR(WRONG_PHASE);
+  }
+
+  // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside,
+  // exceptions are swallowed.
+
+  art::Thread* self = art::Thread::Current();
+  JNIEnv* env = self->GetJniEnv();
+  if (!env->IsInstanceOf(classloader, art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
+    JVMTI_LOG(ERROR, jvmti_env) << "Unable to add " << segment << " to non BaseDexClassLoader!";
+    return ERR(CLASS_LOADER_UNSUPPORTED);
+  }
+
+  jmethodID add_dex_path_id = env->GetMethodID(
+      art::WellKnownClasses::dalvik_system_BaseDexClassLoader,
+      "addDexPath",
+      "(Ljava/lang/String;)V");
+  if (add_dex_path_id == nullptr) {
+    return ERR(INTERNAL);
+  }
+
+  ScopedLocalRef<jstring> dex_path(env, env->NewStringUTF(segment));
+  if (dex_path.get() == nullptr) {
+    return ERR(INTERNAL);
+  }
+  env->CallVoidMethod(classloader, add_dex_path_id, dex_path.get());
+
+  if (env->ExceptionCheck()) {
+    {
+      art::ScopedObjectAccess soa(self);
+      JVMTI_LOG(ERROR, jvmti_env) << "Failed to add " << segment << " to classloader. Error was "
+                                  << self->GetException()->Dump();
+    }
+    env->ExceptionClear();
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  return OK;
+}
+
+jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env, const char* segment) {
   if (segment == nullptr) {
     return ERR(NULL_POINTER);
   }
@@ -266,41 +385,18 @@
     return ERR(WRONG_PHASE);
   }
 
-  jobject sys_class_loader = art::Runtime::Current()->GetSystemClassLoader();
-  if (sys_class_loader == nullptr) {
-    // This is unexpected.
+  jobject loader = art::Runtime::Current()->GetSystemClassLoader();
+  if (loader == nullptr) {
     return ERR(INTERNAL);
   }
 
-  // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside,
-  // exceptions are swallowed.
-
   art::Thread* self = art::Thread::Current();
   JNIEnv* env = self->GetJniEnv();
-  if (!env->IsInstanceOf(sys_class_loader,
-                         art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
+  if (!env->IsInstanceOf(loader, art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
     return ERR(INTERNAL);
   }
 
-  jmethodID add_dex_path_id = env->GetMethodID(
-      art::WellKnownClasses::dalvik_system_BaseDexClassLoader,
-      "addDexPath",
-      "(Ljava/lang/String;)V");
-  if (add_dex_path_id == nullptr) {
-    return ERR(INTERNAL);
-  }
-
-  ScopedLocalRef<jstring> dex_path(env, env->NewStringUTF(segment));
-  if (dex_path.get() == nullptr) {
-    return ERR(INTERNAL);
-  }
-  env->CallVoidMethod(sys_class_loader, add_dex_path_id, dex_path.get());
-
-  if (env->ExceptionCheck()) {
-    env->ExceptionClear();
-    return ERR(ILLEGAL_ARGUMENT);
-  }
-  return ERR(NONE);
+  return AddToDexClassLoader(jvmti_env, loader, segment);
 }
 
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_search.h b/openjdkjvmti/ti_search.h
index 81a28cc..b8d08bf 100644
--- a/openjdkjvmti/ti_search.h
+++ b/openjdkjvmti/ti_search.h
@@ -46,6 +46,12 @@
   static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment);
 
   static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment);
+
+  static jvmtiError AddToDexClassLoader(jvmtiEnv* env, jobject classloader, const char* segment);
+  static jvmtiError AddToDexClassLoaderInMemory(jvmtiEnv* env,
+                                                jobject classloader,
+                                                const char* dex_bytes,
+                                                jint dex_bytes_length);
 };
 
 }  // namespace openjdkjvmti
diff --git a/test/1963-add-to-dex-classloader-in-memory/add_to_loader.cc b/test/1963-add-to-dex-classloader-in-memory/add_to_loader.cc
new file mode 100644
index 0000000..1c3f36d
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/add_to_loader.cc
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1963AddToDexClassLoaderInMemory {
+
+using AddToDexClassLoaderInMemory = jvmtiError (*)(jvmtiEnv* env,
+                                                   jobject loader,
+                                                   const unsigned char* dex_file,
+                                                   jint dex_file_length);
+
+template <typename T> static void Dealloc(T* t) {
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename... Rest> static void Dealloc(T* t, Rest... rs) {
+  Dealloc(t);
+  Dealloc(rs...);
+}
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(params[i].name);
+  }
+}
+
+AddToDexClassLoaderInMemory GetAddFunction(JNIEnv* env) {
+  // Get the extensions.
+  jint n_ext = 0;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+    return nullptr;
+  }
+  AddToDexClassLoaderInMemory result = nullptr;
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp("com.android.art.classloader.add_to_dex_class_loader_in_memory", cur_info->id) ==
+        0) {
+      result = reinterpret_cast<AddToDexClassLoaderInMemory>(cur_info->func);
+    }
+    // Cleanup the cur_info
+    DeallocParams(cur_info->params, cur_info->param_count);
+    Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+  }
+  // Cleanup the array.
+  Dealloc(infos);
+  return result;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1963_addToClassLoaderNative(JNIEnv* env,
+                                                                           jclass,
+                                                                           jobject loader,
+                                                                           jobject bytebuffer) {
+  AddToDexClassLoaderInMemory add_func = GetAddFunction(env);
+  if (add_func == nullptr) {
+    env->ThrowNew(env->FindClass("java/lang/RuntimeError"), "Failed to find extension function");
+    return;
+  }
+  JvmtiErrorToException(
+      env,
+      jvmti_env,
+      add_func(jvmti_env,
+               loader,
+               reinterpret_cast<unsigned char*>(env->GetDirectBufferAddress(bytebuffer)),
+               env->GetDirectBufferCapacity(bytebuffer)));
+}
+
+}  // namespace Test1963AddToDexClassLoaderInMemory
+}  // namespace art
diff --git a/test/1963-add-to-dex-classloader-in-memory/expected.txt b/test/1963-add-to-dex-classloader-in-memory/expected.txt
new file mode 100644
index 0000000..c3cc448
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/expected.txt
@@ -0,0 +1,19 @@
+ - Run while adding new referenced class.
+ -- Running sayHi before redefinition
+Hello from TestClass sayHi function
+Goodbye from TestClass!
+ -- Adding NewClass to classloader!
+ -- Redefine the TestClass
+ -- call TestClass again, now with NewClass refs
+Hello again from TestClass sayHi function
+Hello from NewClass sayHi function
+Goodbye again from TestClass!
+ - Run without adding new referenced class.
+ -- Running sayHi before redefinition
+Hello from TestClass sayHi function
+Goodbye from TestClass!
+ -- Redefine the TestClass
+ -- call TestClass again, now with NewClass refs
+Hello again from TestClass sayHi function
+ -- Exception caught when running test without new class added! java.lang.NoClassDefFoundError
+ --- java.lang.NoClassDefFoundError At foobar.TestClass.sayHi(TestClass.java:5)
diff --git a/test/1963-add-to-dex-classloader-in-memory/info.txt b/test/1963-add-to-dex-classloader-in-memory/info.txt
new file mode 100644
index 0000000..48df982
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/info.txt
@@ -0,0 +1 @@
+Tests we can add dex-file buffers to an existing classloader and the old classes can see them.
\ No newline at end of file
diff --git a/test/1963-add-to-dex-classloader-in-memory/run b/test/1963-add-to-dex-classloader-in-memory/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --jvmti
diff --git a/test/1963-add-to-dex-classloader-in-memory/src/Main.java b/test/1963-add-to-dex-classloader-in-memory/src/Main.java
new file mode 100644
index 0000000..3728b45
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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) throws Exception {
+    art.Test1963.run();
+  }
+}
diff --git a/test/1963-add-to-dex-classloader-in-memory/src/art/Redefinition.java b/test/1963-add-to-dex-classloader-in-memory/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1963-add-to-dex-classloader-in-memory/src/art/Test1963.java b/test/1963-add-to-dex-classloader-in-memory/src/art/Test1963.java
new file mode 100644
index 0000000..a42b3ff
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/src/art/Test1963.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package art;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+
+public final class Test1963 {
+  private static boolean IS_ART = System.getProperty("java.vm.name").equals("Dalvik");
+
+  private static String TEST_CLASS_NAME = "foobar.TestClass";
+  private static String NEW_CLASS_NAME = "foobar.NewClass";
+
+  /**
+   * base64 encoded class/dex file for
+   * package foobar;
+   * public class NewClass {
+   *   static void sayHi() {
+   *    System.out.println("Hello from NewClass sayHi function");
+   *    TestClass.sayBye();
+   *   }
+   * }
+   */
+  private static byte[] NEW_CLASS_BYTES = Base64.getDecoder().decode(
+      "yv66vgAAADUAIQoABwAPCQAQABEIABIKABMAFAoAFQAWBwAXBwAYAQAGPGluaXQ+AQADKClWAQAE"
+      + "Q29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAKU291cmNlRmlsZQEADU5ld0NsYXNzLmph"
+      + "dmEMAAgACQcAGQwAGgAbAQAiSGVsbG8gZnJvbSBOZXdDbGFzcyBzYXlIaSBmdW5jdGlvbgcAHAwA"
+      + "HQAeBwAfDAAgAAkBAA9mb29iYXIvTmV3Q2xhc3MBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9s"
+      + "YW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRT"
+      + "dHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAQZm9vYmFyL1Rlc3RDbGFz"
+      + "cwEABnNheUJ5ZQAhAAYABwAAAAAAAgABAAgACQABAAoAAAAdAAEAAQAAAAUqtwABsQAAAAEACwAA"
+      + "AAYAAQAAAAIACAAMAAkAAQAKAAAALAACAAAAAAAMsgACEgO2AAS4AAWxAAAAAQALAAAADgADAAAA"
+      + "BAAIAAUACwAGAAEADQAAAAIADg==");
+  private static byte[] NEW_DEX_BYTES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQA8kzH5IALCWT88v716WlU7OfqukCT2o6WQAwAAcAAAAHhWNBIAAAAAAAAAAOQCAAAQ"
+      + "AAAAcAAAAAcAAACwAAAAAgAAAMwAAAABAAAA5AAAAAUAAADsAAAAAQAAABQBAABcAgAANAEAAIIB"
+      + "AACKAQAArgEAAMEBAADVAQAA7AEAAAACAAAUAgAAKAIAADcCAAA6AgAAPgIAAEMCAABMAgAAVAIA"
+      + "AFsCAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAAB8AQAA"
+      + "BQACAAsAAAAAAAAAAAAAAAAAAAAOAAAAAQAAAA0AAAACAAEADAAAAAMAAAAAAAAAAAAAAAEAAAAD"
+      + "AAAAAAAAAAgAAAAAAAAA0gIAAAAAAAABAAEAAQAAAHIBAAAEAAAAcBAEAAAADgACAAAAAgAAAHYB"
+      + "AAALAAAAYgAAABoBAQBuIAMAEABxAAIAAAAOAAIADgAEAA54PAABAAAABAAGPGluaXQ+ACJIZWxs"
+      + "byBmcm9tIE5ld0NsYXNzIHNheUhpIGZ1bmN0aW9uABFMZm9vYmFyL05ld0NsYXNzOwASTGZvb2Jh"
+      + "ci9UZXN0Q2xhc3M7ABVMamF2YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAS"
+      + "TGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsADU5ld0NsYXNzLmphdmEAAVYA"
+      + "AlZMAANvdXQAB3ByaW50bG4ABnNheUJ5ZQAFc2F5SGkAdX5+RDh7ImNvbXBpbGF0aW9uLW1vZGUi"
+      + "OiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiZDMyODJiOGY1NDdjMjM0YzRlNGM5MzA5YzM2"
+      + "Yzc5NWEyOTg1NmVhYiIsInZlcnNpb24iOiIxLjYuMS1kZXYifQAAAAIAAIGABLQCAQjMAgAAAAAO"
+      + "AAAAAAAAAAEAAAAAAAAAAQAAABAAAABwAAAAAgAAAAcAAACwAAAAAwAAAAIAAADMAAAABAAAAAEA"
+      + "AADkAAAABQAAAAUAAADsAAAABgAAAAEAAAAUAQAAASAAAAIAAAA0AQAAAyAAAAIAAAByAQAAARAA"
+      + "AAEAAAB8AQAAAiAAABAAAACCAQAAACAAAAEAAADSAgAAAxAAAAEAAADgAgAAABAAAAEAAADkAgAA");
+  /**
+   * base64 encoded class/dex file for
+   * package foobar;
+   * public class TestClass {
+   *   public static void sayHi() {
+   *    System.out.println("Hello again from TestClass sayHi function");
+   *    TestClass.sayBye();
+   *   }
+   *   static void sayBye() {
+   *    System.out.println("Goodbye from TestClass!");
+   *   }
+   * }
+   */
+  private static byte[] CLASS_BYTES = Base64.getDecoder().decode(
+      "yv66vgAAADUAIQoACAARCQASABMIABQKABUAFgoABwAXCAAYBwAZBwAaAQAGPGluaXQ+AQADKClW"
+      + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA"
+      + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABsMABwAHQEAI0hlbGxvIGZyb20gVGVzdENsYXNzIHNheUhp"
+      + "IGZ1bmN0aW9uBwAeDAAfACAMAA4ACgEAF0dvb2RieWUgZnJvbSBUZXN0Q2xhc3MhAQAQZm9vYmFy"
+      + "L1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAV"
+      + "TGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUo"
+      + "TGphdmEvbGFuZy9TdHJpbmc7KVYAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcA"
+      + "AbEAAAABAAwAAAAGAAEAAAACAAkADQAKAAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEA"
+      + "DAAAAA4AAwAAAAQACAAFAAsABgAIAA4ACgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwA"
+      + "AAAKAAIAAAAIAAgACQABAA8AAAACABA=");
+
+  private static byte[] DEX_BYTES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQARmtFTPdWXebnrTNy5b71tEiJKC96qIPXAAwAAcAAAAHhWNBIAAAAAAAAAABQDAAAQ"
+      + "AAAAcAAAAAYAAACwAAAAAgAAAMgAAAABAAAA4AAAAAUAAADoAAAAAQAAABABAACQAgAAMAEAAKYB"
+      + "AACuAQAAxwEAAOwBAAAAAgAAFwIAACsCAAA/AgAAUwIAAGMCAABmAgAAagIAAG8CAAB4AgAAgAIA"
+      + "AIcCAAADAAAABAAAAAUAAAAGAAAABwAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAKABAAAEAAEA"
+      + "CwAAAAAAAAAAAAAAAAAAAA0AAAAAAAAADgAAAAEAAQAMAAAAAgAAAAAAAAAAAAAAAQAAAAIAAAAA"
+      + "AAAACAAAAAAAAAD+AgAAAAAAAAEAAQABAAAAjgEAAAQAAABwEAQAAAAOAAIAAAACAAAAkgEAAAgA"
+      + "AABiAAAAGgEBAG4gAwAQAA4AAgAAAAIAAACXAQAACwAAAGIAAAAaAQIAbiADABAAcQABAAAADgAC"
+      + "AA4ACAAOeAAEAA54PAAAAAABAAAAAwAGPGluaXQ+ABdHb29kYnllIGZyb20gVGVzdENsYXNzIQAj"
+      + "SGVsbG8gZnJvbSBUZXN0Q2xhc3Mgc2F5SGkgZnVuY3Rpb24AEkxmb29iYXIvVGVzdENsYXNzOwAV"
+      + "TGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3Ry"
+      + "aW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA5UZXN0Q2xhc3MuamF2YQABVgACVkwAA291dAAHcHJp"
+      + "bnRsbgAGc2F5QnllAAVzYXlIaQB1fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWlu"
+      + "LWFwaSI6MSwic2hhLTEiOiJkMzI4MmI4ZjU0N2MyMzRjNGU0YzkzMDljMzZjNzk1YTI5ODU2ZWFi"
+      + "IiwidmVyc2lvbiI6IjEuNi4xLWRldiJ9AAAAAwAAgYAEsAIBCMgCAQnoAgAAAAAOAAAAAAAAAAEA"
+      + "AAAAAAAAAQAAABAAAABwAAAAAgAAAAYAAACwAAAAAwAAAAIAAADIAAAABAAAAAEAAADgAAAABQAA"
+      + "AAUAAADoAAAABgAAAAEAAAAQAQAAASAAAAMAAAAwAQAAAyAAAAMAAACOAQAAARAAAAEAAACgAQAA"
+      + "AiAAABAAAACmAQAAACAAAAEAAAD+AgAAAxAAAAEAAAAQAwAAABAAAAEAAAAUAwAA");
+  /**
+   * base64 encoded class/dex file for
+   * package foobar;
+   * public class TestClass {
+   *   public static void sayHi() {
+   *    System.out.println("Hello again from TestClass sayHi function");
+   *    NewClass.sayHi();
+   *   }
+   *   static void sayBye() {
+   *    System.out.println("Goodbye again from TestClass!");
+   *   }
+   * }
+   */
+  private static byte[] REDEF_CLASS_BYTES = Base64.getDecoder().decode(
+      "yv66vgAAADUAIwoACAARCQASABMIABQKABUAFgoAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW"
+      + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA"
+      + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABwMAB0AHgEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNz"
+      + "IHNheUhpIGZ1bmN0aW9uBwAfDAAgACEHACIMAA0ACgEAHUdvb2RieWUgYWdhaW4gZnJvbSBUZXN0"
+      + "Q2xhc3MhAQAQZm9vYmFyL1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcv"
+      + "U3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVh"
+      + "bQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAA9mb29iYXIvTmV3Q2xhc3MAIQAH"
+      + "AAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAACAAkADQAK"
+      + "AAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEADAAAAA4AAwAAAAQACAAFAAsABgAIAA4A"
+      + "CgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwAAAAKAAIAAAAIAAgACQABAA8AAAACABA=");
+
+  private static byte[] REDEF_DEX_BYTES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQA2plEeYRH4vl6wJgnAZOVcZ537QN9NXB3wAwAAcAAAAHhWNBIAAAAAAAAAAEQDAAAR"
+      + "AAAAcAAAAAcAAAC0AAAAAgAAANAAAAABAAAA6AAAAAYAAADwAAAAAQAAACABAACwAgAAQAEAALYB"
+      + "AAC+AQAA3QEAAAgCAAAbAgAALwIAAEYCAABaAgAAbgIAAIICAACSAgAAlQIAAJkCAACeAgAApwIA"
+      + "AK8CAAC2AgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA"
+      + "sAEAAAUAAgAMAAAAAAAAAA8AAAABAAAAAAAAAAEAAAAOAAAAAQAAAA8AAAACAAEADQAAAAMAAAAA"
+      + "AAAAAQAAAAEAAAADAAAAAAAAAAkAAAAAAAAALQMAAAAAAAABAAEAAQAAAJ4BAAAEAAAAcBAFAAAA"
+      + "DgACAAAAAgAAAKIBAAAIAAAAYgAAABoBAQBuIAQAEAAOAAIAAAACAAAApwEAAAsAAABiAAAAGgEC"
+      + "AG4gBAAQAHEAAAAAAA4AAgAOAAgADngABAAOeDwAAAAAAQAAAAQABjxpbml0PgAdR29vZGJ5ZSBh"
+      + "Z2FpbiBmcm9tIFRlc3RDbGFzcyEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNzIHNheUhpIGZ1"
+      + "bmN0aW9uABFMZm9vYmFyL05ld0NsYXNzOwASTGZvb2Jhci9UZXN0Q2xhc3M7ABVMamF2YS9pby9Q"
+      + "cmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2"
+      + "YS9sYW5nL1N5c3RlbTsADlRlc3RDbGFzcy5qYXZhAAFWAAJWTAADb3V0AAdwcmludGxuAAZzYXlC"
+      + "eWUABXNheUhpAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJz"
+      + "aGEtMSI6ImQzMjgyYjhmNTQ3YzIzNGM0ZTRjOTMwOWMzNmM3OTVhMjk4NTZlYWIiLCJ2ZXJzaW9u"
+      + "IjoiMS42LjEtZGV2In0AAAADAAGBgATAAgEI2AIBCfgCAAAAAAAOAAAAAAAAAAEAAAAAAAAAAQAA"
+      + "ABEAAABwAAAAAgAAAAcAAAC0AAAAAwAAAAIAAADQAAAABAAAAAEAAADoAAAABQAAAAYAAADwAAAA"
+      + "BgAAAAEAAAAgAQAAASAAAAMAAABAAQAAAyAAAAMAAACeAQAAARAAAAEAAACwAQAAAiAAABEAAAC2"
+      + "AQAAACAAAAEAAAAtAwAAAxAAAAEAAABAAwAAABAAAAEAAABEAwAA");
+
+  public static void SafePrintCause(Throwable t) {
+    StackTraceElement cause = t.getStackTrace()[0];
+    System.out.println(" --- " + t.getClass().getName() + " At " + cause.getClassName() + "." +
+                       cause.getMethodName() + "(" + cause.getFileName() + ":" +
+                       cause.getLineNumber() + ")");
+  }
+
+  public static void run() throws Exception {
+    System.out.println(" - Run while adding new referenced class.");
+    try {
+      run(true);
+    } catch (Exception e) {
+      // Unfortunately art and RI have different messages here so just return the type.
+      System.out.println(" -- Exception caught when running test with new class added! " +
+                         e.getCause().getClass().getName());
+      SafePrintCause(e.getCause());
+    }
+    System.out.println(" - Run without adding new referenced class.");
+    try {
+      run(false);
+    } catch (Exception e) {
+      // Unfortunately art and RI have different messages here so just return the type.
+      System.out.println(" -- Exception caught when running test without new class added! " +
+                         e.getCause().getClass().getName());
+      SafePrintCause(e.getCause());
+    }
+  }
+
+  public static void run(boolean add_new) throws Exception {
+    ClassLoader cl = getClassLoader();
+    Class<?> target = cl.loadClass(TEST_CLASS_NAME);
+    Method sayHi = target.getDeclaredMethod("sayHi");
+    System.out.println(" -- Running sayHi before redefinition");
+    sayHi.invoke(null);
+    if (add_new) {
+      System.out.println(" -- Adding NewClass to classloader!");
+      addToClassLoader(cl, NEW_CLASS_BYTES, NEW_DEX_BYTES);
+    }
+    System.out.println(" -- Redefine the TestClass");
+    Redefinition.doCommonClassRedefinition(target, REDEF_CLASS_BYTES, REDEF_DEX_BYTES);
+    System.out.println(" -- call TestClass again, now with NewClass refs");
+    sayHi.invoke(null);
+  }
+
+  public static class ExtensibleClassLoader extends ClassLoader {
+    private byte[] new_class = null;
+    public ExtensibleClassLoader() {
+      super(ExtensibleClassLoader.class.getClassLoader());
+    }
+
+    public void addSingleClass(byte[] bb) {
+      new_class = bb;
+    }
+
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+      if (name.equals(TEST_CLASS_NAME)) {
+        return this.defineClass(TEST_CLASS_NAME, CLASS_BYTES, 0, CLASS_BYTES.length);
+      }
+      if (name.equals(NEW_CLASS_NAME) && new_class != null) {
+        return this.defineClass(name, new_class, 0, new_class.length);
+      } else {
+        return super.findClass(name);
+      }
+    }
+  }
+
+  public static ClassLoader getClassLoader() throws Exception {
+    if (!IS_ART) {
+      return new ExtensibleClassLoader();
+    } else {
+      Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader");
+      Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class);
+      return (ClassLoader)ctor.newInstance(ByteBuffer.wrap(DEX_BYTES),
+                                           Test1963.class.getClassLoader());
+    }
+  }
+
+  public static void addToClassLoader(ClassLoader cl, byte[] class_bytes, byte[] dex_bytes) {
+    if (IS_ART) {
+      addToClassLoaderNative(cl, ByteBuffer.allocateDirect(dex_bytes.length).put(dex_bytes));
+    } else {
+      ((ExtensibleClassLoader)cl).addSingleClass(class_bytes);
+    }
+  }
+
+  public static native void addToClassLoaderNative(ClassLoader loader, ByteBuffer buff);
+}
diff --git a/test/1964-add-to-dex-classloader-file/add_to_loader.cc b/test/1964-add-to-dex-classloader-file/add_to_loader.cc
new file mode 100644
index 0000000..9fbea97
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/add_to_loader.cc
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1964AddToDexClassLoader {
+
+using AddToDexClassLoader = jvmtiError (*)(jvmtiEnv* env,
+                                                   jobject loader,
+                                                   const char* segment);
+
+template <typename T> static void Dealloc(T* t) {
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename... Rest> static void Dealloc(T* t, Rest... rs) {
+  Dealloc(t);
+  Dealloc(rs...);
+}
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(params[i].name);
+  }
+}
+
+AddToDexClassLoader GetAddFunction(JNIEnv* env) {
+  // Get the extensions.
+  jint n_ext = 0;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+    return nullptr;
+  }
+  AddToDexClassLoader result = nullptr;
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp("com.android.art.classloader.add_to_dex_class_loader", cur_info->id) ==
+        0) {
+      result = reinterpret_cast<AddToDexClassLoader>(cur_info->func);
+    }
+    // Cleanup the cur_info
+    DeallocParams(cur_info->params, cur_info->param_count);
+    Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+  }
+  // Cleanup the array.
+  Dealloc(infos);
+  return result;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_addToClassLoaderNative(JNIEnv* env,
+                                                                           jclass,
+                                                                           jobject loader,
+                                                                           jstring segment) {
+  AddToDexClassLoader add_func = GetAddFunction(env);
+  if (add_func == nullptr) {
+    env->ThrowNew(env->FindClass("java/lang/RuntimeError"), "Failed to find extension function");
+    return;
+  }
+  const char* chars = env->GetStringUTFChars(segment, nullptr);
+  JvmtiErrorToException(
+      env,
+      jvmti_env,
+      add_func(jvmti_env,
+               loader,
+               chars));
+  env->ReleaseStringUTFChars(segment, chars);
+}
+
+}  // namespace Test1964AddToDexClassLoader
+}  // namespace art
diff --git a/test/1964-add-to-dex-classloader-file/expected.txt b/test/1964-add-to-dex-classloader-file/expected.txt
new file mode 100644
index 0000000..58b86ef
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/expected.txt
@@ -0,0 +1,23 @@
+ - Run while adding new referenced class.
+ -- Running sayHi before redefinition
+Hello from TestClass sayHi function
+Goodbye from TestClass!
+ -- Adding NewClass to classloader!
+ -- Redefine the TestClass
+ -- call TestClass again, now with NewClass refs
+Hello again from TestClass sayHi function
+Hello from NewClass sayHi function
+Nearby stack:
+	private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)(line: -1)
+	public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)(line: 61)
+	static void foobar.NewClass.sayHi() throws java.lang.Exception(line: 27)
+	public static void foobar.TestClass.sayHi()(line: 5)
+ - Run without adding new referenced class.
+ -- Running sayHi before redefinition
+Hello from TestClass sayHi function
+Goodbye from TestClass!
+ -- Redefine the TestClass
+ -- call TestClass again, now with NewClass refs
+Hello again from TestClass sayHi function
+ -- Exception caught when running test without new class added! java.lang.NoClassDefFoundError
+ --- java.lang.NoClassDefFoundError At foobar.TestClass.sayHi(TestClass.java:5)
diff --git a/test/1964-add-to-dex-classloader-file/info.txt b/test/1964-add-to-dex-classloader-file/info.txt
new file mode 100644
index 0000000..48df982
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/info.txt
@@ -0,0 +1 @@
+Tests we can add dex-file buffers to an existing classloader and the old classes can see them.
\ No newline at end of file
diff --git a/test/1964-add-to-dex-classloader-file/run b/test/1964-add-to-dex-classloader-file/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --jvmti
diff --git a/test/1964-add-to-dex-classloader-file/src-ex/foobar/NewClass.java b/test/1964-add-to-dex-classloader-file/src-ex/foobar/NewClass.java
new file mode 100644
index 0000000..a27d5d3
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/src-ex/foobar/NewClass.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package foobar;
+import art.Breakpoint;
+import art.StackTrace;
+
+public class NewClass {
+  static void sayHi() throws Exception {
+    System.out.println("Hello from NewClass sayHi function");
+    // Doing this would be nice but it would make compiling the test more tricky. Just use
+    // reflection and check the classloader is the same.
+    // TestClass.sayBye();
+    StackTrace.StackFrameData[] stack = StackTrace.GetStackTrace(Thread.currentThread());
+    StackTrace.StackFrameData caller = null;
+    System.out.println("Nearby stack:");
+    for (StackTrace.StackFrameData sfd : stack) {
+      String caller_name = sfd.method.getDeclaringClass().getName();
+      if (caller_name.startsWith("art.") || caller_name.startsWith("foobar.")) {
+        System.out.println("\t" + sfd.method + "(line: " +
+                           Breakpoint.locationToLine(sfd.method, sfd.current_location) + ")");
+        caller = sfd;
+      } else {
+        break;
+      }
+    }
+    if (NewClass.class.getClassLoader() != caller.method.getDeclaringClass().getClassLoader()) {
+      System.out.println("Different classloader for TestClass and my class.");
+    }
+  }
+}
\ No newline at end of file
diff --git a/test/1964-add-to-dex-classloader-file/src/Main.java b/test/1964-add-to-dex-classloader-file/src/Main.java
new file mode 100644
index 0000000..2293d42
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/src/Main.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 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 art.Redefinition;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+
+public class Main {
+  private static String TEST_NAME = "1964-add-to-dex-classloader-file";
+  private static boolean IS_ART = System.getProperty("java.vm.name").equals("Dalvik");
+
+  private static String TEST_CLASS_NAME = "foobar.TestClass";
+  private static String NEW_CLASS_NAME = "foobar.NewClass";
+
+  /**
+   * base64 encoded class/dex file for
+   * package foobar;
+   * public class TestClass {
+   *   public static void sayHi() {
+   *    System.out.println("Hello again from TestClass sayHi function");
+   *    TestClass.sayBye();
+   *   }
+   *   static void sayBye() {
+   *    System.out.println("Goodbye from TestClass!");
+   *   }
+   * }
+   */
+  private static byte[] CLASS_BYTES = Base64.getDecoder().decode(
+      "yv66vgAAADUAIQoACAARCQASABMIABQKABUAFgoABwAXCAAYBwAZBwAaAQAGPGluaXQ+AQADKClW"
+      + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA"
+      + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABsMABwAHQEAI0hlbGxvIGZyb20gVGVzdENsYXNzIHNheUhp"
+      + "IGZ1bmN0aW9uBwAeDAAfACAMAA4ACgEAF0dvb2RieWUgZnJvbSBUZXN0Q2xhc3MhAQAQZm9vYmFy"
+      + "L1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAV"
+      + "TGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUo"
+      + "TGphdmEvbGFuZy9TdHJpbmc7KVYAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcA"
+      + "AbEAAAABAAwAAAAGAAEAAAACAAkADQAKAAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEA"
+      + "DAAAAA4AAwAAAAQACAAFAAsABgAIAA4ACgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwA"
+      + "AAAKAAIAAAAIAAgACQABAA8AAAACABA=");
+
+  private static byte[] DEX_BYTES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQARmtFTPdWXebnrTNy5b71tEiJKC96qIPXAAwAAcAAAAHhWNBIAAAAAAAAAABQDAAAQ"
+      + "AAAAcAAAAAYAAACwAAAAAgAAAMgAAAABAAAA4AAAAAUAAADoAAAAAQAAABABAACQAgAAMAEAAKYB"
+      + "AACuAQAAxwEAAOwBAAAAAgAAFwIAACsCAAA/AgAAUwIAAGMCAABmAgAAagIAAG8CAAB4AgAAgAIA"
+      + "AIcCAAADAAAABAAAAAUAAAAGAAAABwAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAKABAAAEAAEA"
+      + "CwAAAAAAAAAAAAAAAAAAAA0AAAAAAAAADgAAAAEAAQAMAAAAAgAAAAAAAAAAAAAAAQAAAAIAAAAA"
+      + "AAAACAAAAAAAAAD+AgAAAAAAAAEAAQABAAAAjgEAAAQAAABwEAQAAAAOAAIAAAACAAAAkgEAAAgA"
+      + "AABiAAAAGgEBAG4gAwAQAA4AAgAAAAIAAACXAQAACwAAAGIAAAAaAQIAbiADABAAcQABAAAADgAC"
+      + "AA4ACAAOeAAEAA54PAAAAAABAAAAAwAGPGluaXQ+ABdHb29kYnllIGZyb20gVGVzdENsYXNzIQAj"
+      + "SGVsbG8gZnJvbSBUZXN0Q2xhc3Mgc2F5SGkgZnVuY3Rpb24AEkxmb29iYXIvVGVzdENsYXNzOwAV"
+      + "TGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3Ry"
+      + "aW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA5UZXN0Q2xhc3MuamF2YQABVgACVkwAA291dAAHcHJp"
+      + "bnRsbgAGc2F5QnllAAVzYXlIaQB1fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWlu"
+      + "LWFwaSI6MSwic2hhLTEiOiJkMzI4MmI4ZjU0N2MyMzRjNGU0YzkzMDljMzZjNzk1YTI5ODU2ZWFi"
+      + "IiwidmVyc2lvbiI6IjEuNi4xLWRldiJ9AAAAAwAAgYAEsAIBCMgCAQnoAgAAAAAOAAAAAAAAAAEA"
+      + "AAAAAAAAAQAAABAAAABwAAAAAgAAAAYAAACwAAAAAwAAAAIAAADIAAAABAAAAAEAAADgAAAABQAA"
+      + "AAUAAADoAAAABgAAAAEAAAAQAQAAASAAAAMAAAAwAQAAAyAAAAMAAACOAQAAARAAAAEAAACgAQAA"
+      + "AiAAABAAAACmAQAAACAAAAEAAAD+AgAAAxAAAAEAAAAQAwAAABAAAAEAAAAUAwAA");
+  /**
+   * base64 encoded class/dex file for
+   * package foobar;
+   * public class TestClass {
+   *   public static void sayHi() {
+   *    System.out.println("Hello again from TestClass sayHi function");
+   *    NewClass.sayHi();
+   *   }
+   *   static void sayBye() {
+   *    System.out.println("Goodbye again from TestClass!");
+   *   }
+   * }
+   */
+  private static byte[] REDEF_CLASS_BYTES = Base64.getDecoder().decode(
+      "yv66vgAAADUAIwoACAARCQASABMIABQKABUAFgoAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW"
+      + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAGc2F5QnllAQAKU291cmNlRmlsZQEA"
+      + "DlRlc3RDbGFzcy5qYXZhDAAJAAoHABwMAB0AHgEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNz"
+      + "IHNheUhpIGZ1bmN0aW9uBwAfDAAgACEHACIMAA0ACgEAHUdvb2RieWUgYWdhaW4gZnJvbSBUZXN0"
+      + "Q2xhc3MhAQAQZm9vYmFyL1Rlc3RDbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcv"
+      + "U3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVh"
+      + "bQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAA9mb29iYXIvTmV3Q2xhc3MAIQAH"
+      + "AAgAAAAAAAMAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAACAAkADQAK"
+      + "AAEACwAAACwAAgAAAAAADLIAAhIDtgAEuAAFsQAAAAEADAAAAA4AAwAAAAQACAAFAAsABgAIAA4A"
+      + "CgABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwAAAAKAAIAAAAIAAgACQABAA8AAAACABA=");
+
+  private static byte[] REDEF_DEX_BYTES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQA2plEeYRH4vl6wJgnAZOVcZ537QN9NXB3wAwAAcAAAAHhWNBIAAAAAAAAAAEQDAAAR"
+      + "AAAAcAAAAAcAAAC0AAAAAgAAANAAAAABAAAA6AAAAAYAAADwAAAAAQAAACABAACwAgAAQAEAALYB"
+      + "AAC+AQAA3QEAAAgCAAAbAgAALwIAAEYCAABaAgAAbgIAAIICAACSAgAAlQIAAJkCAACeAgAApwIA"
+      + "AK8CAAC2AgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA"
+      + "sAEAAAUAAgAMAAAAAAAAAA8AAAABAAAAAAAAAAEAAAAOAAAAAQAAAA8AAAACAAEADQAAAAMAAAAA"
+      + "AAAAAQAAAAEAAAADAAAAAAAAAAkAAAAAAAAALQMAAAAAAAABAAEAAQAAAJ4BAAAEAAAAcBAFAAAA"
+      + "DgACAAAAAgAAAKIBAAAIAAAAYgAAABoBAQBuIAQAEAAOAAIAAAACAAAApwEAAAsAAABiAAAAGgEC"
+      + "AG4gBAAQAHEAAAAAAA4AAgAOAAgADngABAAOeDwAAAAAAQAAAAQABjxpbml0PgAdR29vZGJ5ZSBh"
+      + "Z2FpbiBmcm9tIFRlc3RDbGFzcyEAKUhlbGxvIGFnYWluIGZyb20gVGVzdENsYXNzIHNheUhpIGZ1"
+      + "bmN0aW9uABFMZm9vYmFyL05ld0NsYXNzOwASTGZvb2Jhci9UZXN0Q2xhc3M7ABVMamF2YS9pby9Q"
+      + "cmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2"
+      + "YS9sYW5nL1N5c3RlbTsADlRlc3RDbGFzcy5qYXZhAAFWAAJWTAADb3V0AAdwcmludGxuAAZzYXlC"
+      + "eWUABXNheUhpAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJz"
+      + "aGEtMSI6ImQzMjgyYjhmNTQ3YzIzNGM0ZTRjOTMwOWMzNmM3OTVhMjk4NTZlYWIiLCJ2ZXJzaW9u"
+      + "IjoiMS42LjEtZGV2In0AAAADAAGBgATAAgEI2AIBCfgCAAAAAAAOAAAAAAAAAAEAAAAAAAAAAQAA"
+      + "ABEAAABwAAAAAgAAAAcAAAC0AAAAAwAAAAIAAADQAAAABAAAAAEAAADoAAAABQAAAAYAAADwAAAA"
+      + "BgAAAAEAAAAgAQAAASAAAAMAAABAAQAAAyAAAAMAAACeAQAAARAAAAEAAACwAQAAAiAAABEAAAC2"
+      + "AQAAACAAAAEAAAAtAwAAAxAAAAEAAABAAwAAABAAAAEAAABEAwAA");
+
+  public static void SafePrintCause(Throwable t) {
+    StackTraceElement cause = t.getStackTrace()[0];
+    System.out.println(" --- " + t.getClass().getName() + " At " + cause.getClassName() + "." +
+                       cause.getMethodName() + "(" + cause.getFileName() + ":" +
+                       cause.getLineNumber() + ")");
+  }
+
+  public static void run() throws Exception {
+    System.out.println(" - Run while adding new referenced class.");
+    try {
+      run(true);
+    } catch (Exception e) {
+      // Unfortunately art and RI have different messages here so just return the type.
+      System.out.println(" -- Exception caught when running test with new class added! " +
+                         e.getCause().getClass().getName());
+      SafePrintCause(e.getCause());
+      System.out.println(e);
+      e.printStackTrace();
+    }
+    System.out.println(" - Run without adding new referenced class.");
+    try {
+      run(false);
+    } catch (Exception e) {
+      // Unfortunately art and RI have different messages here so just return the type.
+      System.out.println(" -- Exception caught when running test without new class added! " +
+                         e.getCause().getClass().getName());
+      SafePrintCause(e.getCause());
+    }
+  }
+
+  public static void run(boolean add_new) throws Exception {
+    ClassLoader cl = getClassLoader();
+    Class<?> target = cl.loadClass(TEST_CLASS_NAME);
+    Method sayHi = target.getDeclaredMethod("sayHi");
+    System.out.println(" -- Running sayHi before redefinition");
+    sayHi.invoke(null);
+    if (add_new) {
+      System.out.println(" -- Adding NewClass to classloader!");
+      addToClassLoader(cl);
+    }
+    System.out.println(" -- Redefine the TestClass");
+    Redefinition.doCommonClassRedefinition(target, REDEF_CLASS_BYTES, REDEF_DEX_BYTES);
+    System.out.println(" -- call TestClass again, now with NewClass refs");
+    sayHi.invoke(null);
+  }
+
+  public static class ExtensibleClassLoader extends URLClassLoader {
+    public ExtensibleClassLoader() {
+      // Initially we don't have any URLs
+      super(new URL[] {}, ExtensibleClassLoader.class.getClassLoader());
+    }
+
+    public void addSingleUrl(String file) throws Exception {
+      this.addURL(new URL("file://" + file));
+    }
+
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+      // Just define the TestClass without other jars.
+      if (name.equals(TEST_CLASS_NAME)) {
+        return this.defineClass(TEST_CLASS_NAME, CLASS_BYTES, 0, CLASS_BYTES.length);
+      } else {
+        return super.findClass(name);
+      }
+    }
+  }
+
+  public static ClassLoader getClassLoader() throws Exception {
+    if (!IS_ART) {
+      return new ExtensibleClassLoader();
+    } else {
+      Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader");
+      Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class);
+      return (ClassLoader)ctor.newInstance(ByteBuffer.wrap(DEX_BYTES), Main.class.getClassLoader());
+    }
+  }
+
+  public static void addToClassLoader(ClassLoader cl) throws Exception {
+    if (IS_ART) {
+      addToClassLoaderNative(cl, System.getenv("DEX_LOCATION") + "/" + TEST_NAME + "-ex.jar");
+    } else {
+      ((ExtensibleClassLoader)cl).addSingleUrl(System.getenv("DEX_LOCATION") + "/classes-ex/");
+    }
+  }
+
+  public static native void addToClassLoaderNative(ClassLoader loader, String segment);
+  public static void main(String[] args) throws Exception {
+    run();
+  }
+}
diff --git a/test/1964-add-to-dex-classloader-file/src/art/Breakpoint.java b/test/1964-add-to-dex-classloader-file/src/art/Breakpoint.java
new file mode 120000
index 0000000..3673916
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/src/art/Breakpoint.java
@@ -0,0 +1 @@
+../../../jvmti-common/Breakpoint.java
\ No newline at end of file
diff --git a/test/1964-add-to-dex-classloader-file/src/art/Redefinition.java b/test/1964-add-to-dex-classloader-file/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1964-add-to-dex-classloader-file/src/art/StackTrace.java b/test/1964-add-to-dex-classloader-file/src/art/StackTrace.java
new file mode 120000
index 0000000..e1a08aa
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/src/art/StackTrace.java
@@ -0,0 +1 @@
+../../../jvmti-common/StackTrace.java
\ No newline at end of file
diff --git a/test/1964-add-to-dex-classloader-file/src/art/Suspension.java b/test/1964-add-to-dex-classloader-file/src/art/Suspension.java
new file mode 120000
index 0000000..bcef96f
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/src/art/Suspension.java
@@ -0,0 +1 @@
+../../../jvmti-common/Suspension.java
\ No newline at end of file
diff --git a/test/Android.bp b/test/Android.bp
index 5ebca66..7f25a6a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -294,6 +294,7 @@
         "1953-pop-frame/pop_frame.cc",
         "1957-error-ext/lasterror.cc",
         "1962-multi-thread-events/multi_thread_events.cc",
+        "1963-add-to-dex-classloader-in-memory/add_to_loader.cc",
     ],
     // Use NDK-compatible headers for ctstiagent.
     header_libs: [
@@ -326,6 +327,7 @@
         // "1952-pop-frame-jit/pop_frame.cc",
         "1959-redefine-object-instrument/fake_redef_object.cc",
         "1960-obsolete-jit-multithread-native/native_say_hi.cc",
+        "1964-add-to-dex-classloader-file/add_to_loader.cc",
     ],
     static_libs: [
         "libz",