Add a get_last_error_message JVMTI extension.

This adds a com.android.art.misc.get_last_error_message and
com.android.art.misc.clear_last_error_message extension functions.
These allow one to get some error messages that were previously only
exposed through logcat. Not all error messages are exposed. Only
exposes error messages associated with the exact jvmtiEnv used. Errors
must be cleared manually. Not all error conditions will update the
saved error-message.

Bug: 117234143
Test: ./test.py --host
Change-Id: I75b6de9029791035f56c0c63d8958edea500715d
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
index d8902d6..7621d48 100644
--- a/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -41,6 +41,7 @@
         "ti_field.cc",
         "ti_heap.cc",
         "ti_jni.cc",
+        "ti_logging.cc",
         "ti_method.cc",
         "ti_monitor.cc",
         "ti_object.cc",
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 4bc33b6..a2fabbf 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -58,6 +58,7 @@
 #include "ti_field.h"
 #include "ti_heap.h"
 #include "ti_jni.h"
+#include "ti_logging.h"
 #include "ti_method.h"
 #include "ti_monitor.h"
 #include "ti_object.h"
@@ -787,7 +788,7 @@
                                                      classes,
                                                      &error_msg);
     if (res != OK) {
-      LOG(WARNING) << "FAILURE TO RETRANFORM " << error_msg;
+      JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANFORM " << error_msg;
     }
     return res;
   }
@@ -806,7 +807,7 @@
                                                 class_definitions,
                                                 &error_msg);
     if (res != OK) {
-      LOG(WARNING) << "FAILURE TO REDEFINE " << error_msg;
+      JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg;
     }
     return res;
   }
@@ -1489,7 +1490,8 @@
       local_data(nullptr),
       ti_version(version),
       capabilities(),
-      event_info_mutex_("jvmtiEnv_EventInfoMutex") {
+      event_info_mutex_("jvmtiEnv_EventInfoMutex"),
+      last_error_mutex_("jvmtiEnv_LastErrorMutex", art::LockLevel::kGenericBottomLock) {
   object_tag_table = std::unique_ptr<ObjectTagTable>(new ObjectTagTable(event_handler, this));
   functions = &gJvmtiInterface;
 }
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 1218e3b..7433e54 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -102,6 +102,10 @@
   // RW lock to protect access to all of the event data.
   art::ReaderWriterMutex event_info_mutex_ DEFAULT_MUTEX_ACQUIRED_AFTER;
 
+  std::string last_error_ GUARDED_BY(last_error_mutex_);
+  // Lock to touch the last-error-message.
+  art::Mutex last_error_mutex_ BOTTOM_MUTEX_ACQUIRED_AFTER;
+
   ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler, jint ti_version);
 
   static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) {
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 3d33487..e469270 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -73,6 +73,7 @@
 #include "thread_list.h"
 #include "ti_class_definition.h"
 #include "ti_class_loader-inl.h"
+#include "ti_logging.h"
 #include "ti_phase.h"
 #include "ti_redefine.h"
 #include "transform.h"
@@ -932,8 +933,8 @@
     return ERR(ILLEGAL_ARGUMENT);
   } else if (!jnienv->IsInstanceOf(loader,
                                    art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
-    LOG(ERROR) << "GetClassLoaderClassDescriptors is only implemented for BootClassPath and "
-               << "dalvik.system.BaseDexClassLoader class loaders";
+    JVMTI_LOG(ERROR, env) << "GetClassLoaderClassDescriptors is only implemented for "
+                          << "BootClassPath and dalvik.system.BaseDexClassLoader class loaders";
     // TODO Possibly return OK With no classes would  be better since these ones cannot have any
     // real classes associated with them.
     return ERR(NOT_IMPLEMENTED);
diff --git a/openjdkjvmti/ti_ddms.cc b/openjdkjvmti/ti_ddms.cc
index bf063fa..9de5cbc 100644
--- a/openjdkjvmti/ti_ddms.cc
+++ b/openjdkjvmti/ti_ddms.cc
@@ -39,6 +39,7 @@
 #include "debugger.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
+#include "ti_logging.h"
 
 namespace openjdkjvmti {
 
@@ -69,7 +70,7 @@
                                 data_arr,
                                 /*out*/reinterpret_cast<uint32_t*>(type_out),
                                 /*out*/&out_data)) {
-    LOG(WARNING) << "Something went wrong with handling the ddm chunk.";
+    JVMTI_LOG(WARNING, env) << "Something went wrong with handling the ddm chunk.";
     return ERR(INTERNAL);
   } else {
     jvmtiError error = OK;
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index c628a32..5d39884 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -39,7 +39,9 @@
 #include "ti_class.h"
 #include "ti_ddms.h"
 #include "ti_heap.h"
+#include "ti_logging.h"
 #include "ti_monitor.h"
+
 #include "thread-inl.h"
 
 namespace openjdkjvmti {
@@ -272,6 +274,44 @@
   if (error != ERR(NONE)) {
     return error;
   }
+
+  // GetLastError extension
+  error = add_extension(
+      reinterpret_cast<jvmtiExtensionFunction>(LogUtil::GetLastError),
+      "com.android.art.misc.get_last_error_message",
+      "In some cases the jvmti plugin will log data about errors to the android logcat. These can"
+      " be useful to tools so we make (some) of the messages available here as well. This will"
+      " fill the given 'msg' buffer with the last non-fatal message associated with this"
+      " jvmti-env. Note this is best-effort only, not all log messages will be accessible through"
+      " this API. This will return the last error-message from all threads. Care should be taken"
+      " interpreting the return value when used with a multi-threaded program. The error message"
+      " will only be cleared by a call to 'com.android.art.misc.clear_last_error_message' and will"
+      " not be cleared by intervening successful calls. If no (tracked) error message has been"
+      " sent since the last call to clear_last_error_message this API will return"
+      " JVMTI_ERROR_ABSENT_INFORMATION. Not all failures will cause an error message to be"
+      " recorded.",
+      {
+          { "msg", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, false },
+      },
+      {
+        ERR(NULL_POINTER),
+        ERR(ABSENT_INFORMATION),
+      });
+  if (error != ERR(NONE)) {
+    return error;
+  }
+
+  // ClearLastError extension
+  error = add_extension(
+      reinterpret_cast<jvmtiExtensionFunction>(LogUtil::ClearLastError),
+      "com.android.art.misc.clear_last_error_message",
+      "Clears the error message returned by 'com.android.art.misc.get_last_error_message'.",
+      { },
+      { });
+  if (error != ERR(NONE)) {
+    return error;
+  }
+
   // Copy into output buffer.
 
   *extension_count_ptr = ext_vector.size();
diff --git a/openjdkjvmti/ti_logging.cc b/openjdkjvmti/ti_logging.cc
new file mode 100644
index 0000000..1d24d3b
--- /dev/null
+++ b/openjdkjvmti/ti_logging.cc
@@ -0,0 +1,71 @@
+/* Copyright (C) 2018 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_logging.h"
+
+#include "art_jvmti.h"
+
+#include "base/mutex.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError LogUtil::GetLastError(jvmtiEnv* env, char** data) {
+  if (env == nullptr || data == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  }
+  ArtJvmTiEnv* tienv = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+  art::MutexLock mu(art::Thread::Current(), tienv->last_error_mutex_);
+  if (tienv->last_error_.empty()) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  char* out;
+  jvmtiError err = tienv->Allocate(tienv->last_error_.size() + 1,
+                                   reinterpret_cast<unsigned char**>(&out));
+  if (err != OK) {
+    return err;
+  }
+  strcpy(out, tienv->last_error_.c_str());
+  *data = out;
+  return OK;
+}
+
+jvmtiError LogUtil::ClearLastError(jvmtiEnv* env) {
+  if (env == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  }
+  ArtJvmTiEnv* tienv = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+  art::MutexLock mu(art::Thread::Current(), tienv->last_error_mutex_);
+  tienv->last_error_.clear();
+  return OK;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_logging.h b/openjdkjvmti/ti_logging.h
new file mode 100644
index 0000000..31b51bb
--- /dev/null
+++ b/openjdkjvmti/ti_logging.h
@@ -0,0 +1,102 @@
+/* Copyright (C) 2018 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_OPENJDKJVMTI_TI_LOGGING_H_
+#define ART_OPENJDKJVMTI_TI_LOGGING_H_
+
+#include "art_jvmti.h"
+
+#include <ostream>
+#include <sstream>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+// NB Uses implementation details of android-base/logging.h.
+#define JVMTI_LOG(severity, env)                             \
+  ::openjdkjvmti::JvmtiLogMessage((env),                     \
+                                  __FILE__,                  \
+                                  __LINE__,                  \
+                                  ::android::base::DEFAULT,  \
+                                  SEVERITY_LAMBDA(severity), \
+                                  _LOG_TAG_INTERNAL,         \
+                                  -1)
+
+class JvmtiLogMessage {
+ public:
+  JvmtiLogMessage(jvmtiEnv* env,
+                  const char* file,
+                  unsigned int line,
+                  android::base::LogId id,
+                  android::base::LogSeverity severity,
+                  const char* tag,
+                  int error)
+      : env_(ArtJvmTiEnv::AsArtJvmTiEnv(env)),
+        real_log_(file, line, id, severity, tag, error),
+        real_log_stream_(real_log_.stream()) {
+    DCHECK(env_ != nullptr);
+  }
+
+  ~JvmtiLogMessage() {
+    art::MutexLock mu(art::Thread::Current(), env_->last_error_mutex_);
+    env_->last_error_ = save_stream_.str();
+  }
+
+  template<typename T>
+  JvmtiLogMessage& operator<<(T t) {
+    (real_log_stream_ << t);
+    (save_stream_ << t);
+    return *this;
+  }
+
+ private:
+  ArtJvmTiEnv* env_;
+  android::base::LogMessage real_log_;
+  // Lifetime of real_log_stream_ is lifetime of real_log_.
+  std::ostream& real_log_stream_;
+  std::ostringstream save_stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(JvmtiLogMessage);
+};
+
+class LogUtil {
+ public:
+  static jvmtiError ClearLastError(jvmtiEnv* env);
+  static jvmtiError GetLastError(jvmtiEnv* env, char** data);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_LOGGING_H_
diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
index 427869e..2187825 100644
--- a/openjdkjvmti/ti_search.cc
+++ b/openjdkjvmti/ti_search.cc
@@ -52,6 +52,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
+#include "ti_logging.h"
 #include "ti_phase.h"
 #include "well_known_classes.h"
 
@@ -213,7 +214,7 @@
   runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gSearchCallback);
 }
 
-jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env,
                                                        const char* segment) {
   art::Runtime* current = art::Runtime::Current();
   if (current == nullptr) {
@@ -235,7 +236,8 @@
                             /* verify_checksum= */ true,
                             &error_msg,
                             &dex_files)) {
-    LOG(WARNING) << "Could not open " << segment << " for boot classpath extension: " << error_msg;
+    JVMTI_LOG(WARNING, env) << "Could not open " << segment << " for boot classpath extension: "
+                            << error_msg;
     return ERR(ILLEGAL_ARGUMENT);
   }
 
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
index 1279f3b..5de4a81 100644
--- a/openjdkjvmti/ti_stack.cc
+++ b/openjdkjvmti/ti_stack.cc
@@ -57,6 +57,7 @@
 #include "nativehelper/scoped_local_ref.h"
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
+#include "ti_logging.h"
 #include "ti_thread.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
@@ -1097,7 +1098,7 @@
   } while (true);
 }
 
-jvmtiError StackUtil::PopFrame(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) {
+jvmtiError StackUtil::PopFrame(jvmtiEnv* env, jthread thread) {
   art::Thread* self = art::Thread::Current();
   art::Thread* target;
   do {
@@ -1131,9 +1132,10 @@
         tls_data->disable_pop_frame_depth != JvmtiGlobalTLSData::kNoDisallowedPopFrame &&
         tls_data->disable_pop_frame_depth == art::StackVisitor::ComputeNumFrames(target,
                                                                                  kWalkKind)) {
-      LOG(WARNING) << "Disallowing frame pop due to in-progress class-load/prepare. Frame at depth "
-                   << tls_data->disable_pop_frame_depth << " was marked as un-poppable by the "
-                   << "jvmti plugin. See b/117615146 for more information.";
+      JVMTI_LOG(WARNING, env) << "Disallowing frame pop due to in-progress class-load/prepare. "
+                              << "Frame at depth " << tls_data->disable_pop_frame_depth << " was "
+                              << "marked as un-poppable by the jvmti plugin. See b/117615146 for "
+                              << "more information.";
       return ERR(OPAQUE_FRAME);
     }
     // We hold the user_code_suspension_lock_ so the target thread is staying suspended until we are
diff --git a/test/1957-error-ext/expected.txt b/test/1957-error-ext/expected.txt
new file mode 100644
index 0000000..bfe7033
--- /dev/null
+++ b/test/1957-error-ext/expected.txt
@@ -0,0 +1,4 @@
+LastError is: <call returned error: class java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION>
+Got class java.lang.Exception: Failed to redefine class <Lart/Test1957$Transform;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED
+LastError is: FAILURE TO REDEFINE Unable to perform redefinition of 'Lart/Test1957$Transform;': Total number of declared methods changed from 2 to 1
+LastError is: <call returned error: class java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION>
diff --git a/test/1957-error-ext/info.txt b/test/1957-error-ext/info.txt
new file mode 100644
index 0000000..ef772d9
--- /dev/null
+++ b/test/1957-error-ext/info.txt
@@ -0,0 +1 @@
+Test for get_last_error_message extension function.
diff --git a/test/1957-error-ext/lasterror.cc b/test/1957-error-ext/lasterror.cc
new file mode 100644
index 0000000..5aa3fbe
--- /dev/null
+++ b/test/1957-error-ext/lasterror.cc
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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 <cstdio>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+// Test infrastructure
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test1957ErrorExt {
+
+using GetLastError = jvmtiError(*)(jvmtiEnv* env, char** msg);
+using ClearLastError = jvmtiError(*)(jvmtiEnv* env);
+
+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);
+  }
+}
+
+static jvmtiExtensionFunction FindExtensionMethod(JNIEnv* env, const std::string& name) {
+  jint n_ext;
+  jvmtiExtensionFunctionInfo* infos;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+    return nullptr;
+  }
+  jvmtiExtensionFunction res = nullptr;
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp(name.c_str(), cur_info->id) == 0) {
+      res = 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);
+  if (res == nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), (name + " extensions not found").c_str());
+    return nullptr;
+  }
+  return res;
+}
+
+extern "C" JNIEXPORT
+jstring JNICALL Java_art_Test1957_getLastError(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  GetLastError get_last_error = reinterpret_cast<GetLastError>(
+      FindExtensionMethod(env, "com.android.art.misc.get_last_error_message"));
+  if (get_last_error == nullptr) {
+    return nullptr;
+  }
+  char* msg;
+  if (JvmtiErrorToException(env, jvmti_env, get_last_error(jvmti_env, &msg))) {
+    return nullptr;
+  }
+
+  return env->NewStringUTF(msg);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1957_clearLastError(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  ClearLastError clear_last_error = reinterpret_cast<ClearLastError>(
+      FindExtensionMethod(env, "com.android.art.misc.clear_last_error_message"));
+  if (clear_last_error == nullptr) {
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, clear_last_error(jvmti_env));
+}
+
+}  // namespace Test1957ErrorExt
+}  // namespace art
diff --git a/test/1957-error-ext/run b/test/1957-error-ext/run
new file mode 100755
index 0000000..8be0ed4
--- /dev/null
+++ b/test/1957-error-ext/run
@@ -0,0 +1,18 @@
+#!/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/1957-error-ext/src/Main.java b/test/1957-error-ext/src/Main.java
new file mode 100644
index 0000000..7e5e075
--- /dev/null
+++ b/test/1957-error-ext/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1957.run();
+  }
+}
diff --git a/test/1957-error-ext/src/art/Redefinition.java b/test/1957-error-ext/src/art/Redefinition.java
new file mode 100644
index 0000000..56d2938
--- /dev/null
+++ b/test/1957-error-ext/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/1957-error-ext/src/art/Test1957.java b/test/1957-error-ext/src/art/Test1957.java
new file mode 100644
index 0000000..ffb68be
--- /dev/null
+++ b/test/1957-error-ext/src/art/Test1957.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test1957 {
+
+  static 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");
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * class Transform {
+   * }
+   */
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADUAEQoAAwAKBwAMBwAPAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJU" +
+    "YWJsZQEAClNvdXJjZUZpbGUBAA1UZXN0MTk1Ny5qYXZhDAAEAAUHABABABZhcnQvVGVzdDE5NTck" +
+    "VHJhbnNmb3JtAQAJVHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVjdAEA" +
+    "DGFydC9UZXN0MTk1NwAgAAIAAwAAAAAAAQAAAAQABQABAAYAAAAdAAEAAQAAAAUqtwABsQAAAAEA" +
+    "BwAAAAYAAQAAAAYAAgAIAAAAAgAJAA4AAAAKAAEAAgALAA0ACA==");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQAQiK+oahCb4T18bDge0pSvp7rka4UQ2AY0AwAAcAAAAHhWNBIAAAAAAAAAAIgCAAAN" +
+    "AAAAcAAAAAYAAACkAAAAAQAAALwAAAAAAAAAAAAAAAIAAADIAAAAAQAAANgAAAA8AgAA+AAAABQB" +
+    "AAAcAQAANgEAAEYBAABqAQAAigEAAJ4BAACtAQAAuAEAALsBAADIAQAAzgEAANUBAAABAAAAAgAA" +
+    "AAMAAAAEAAAABQAAAAgAAAAIAAAABQAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAA" +
+    "AAAAAAYAAAB4AgAAWwIAAAAAAAABAAEAAQAAABABAAAEAAAAcBABAAAADgAGAA4ABjxpbml0PgAY" +
+    "TGFydC9UZXN0MTk1NyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTU3OwAiTGRhbHZpay9hbm5vdGF0" +
+    "aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2" +
+    "YS9sYW5nL09iamVjdDsADVRlc3QxOTU3LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MA" +
+    "BG5hbWUABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjox" +
+    "LCJzaGEtMSI6Ijg0NjI2ZDE0MmRiMmY4NzVhY2E2YjVlOWVmYWU3OThjYWQ5ZDlhNTAiLCJ2ZXJz" +
+    "aW9uIjoiMS40LjItZGV2In0AAgIBCxgBAgMCCQQIChcHAAABAACAgAT4AQAAAAAAAAACAAAATAIA" +
+    "AFICAABsAgAAAAAAAAAAAAAAAAAADgAAAAAAAAABAAAAAAAAAAEAAAANAAAAcAAAAAIAAAAGAAAA" +
+    "pAAAAAMAAAABAAAAvAAAAAUAAAACAAAAyAAAAAYAAAABAAAA2AAAAAEgAAABAAAA+AAAAAMgAAAB" +
+    "AAAAEAEAAAIgAAANAAAAFAEAAAQgAAACAAAATAIAAAAgAAABAAAAWwIAAAMQAAACAAAAaAIAAAYg" +
+    "AAABAAAAeAIAAAAQAAABAAAAiAIAAA==");
+
+  public static void run() {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    Transform t = new Transform();
+    System.out.println("LastError is: " + getLastErrorOrException());
+    try {
+      Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+    } catch (Throwable e) {
+      System.out.println("Got " + e.getClass().toString() + ": " + e.getMessage());
+    }
+    System.out.println("LastError is: " + getLastErrorOrException());
+    clearLastError();
+    System.out.println("LastError is: " + getLastErrorOrException());
+  }
+
+  public static String getLastErrorOrException() {
+    try {
+      return getLastError();
+    } catch (Throwable t) {
+      return "<call returned error: " + t.getClass().toString() + ": " + t.getMessage() + ">";
+    }
+  }
+  public static native String getLastError();
+  public static native void clearLastError();
+}
diff --git a/test/Android.bp b/test/Android.bp
index 8c1c1bf..561f95e 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -292,6 +292,7 @@
         "1950-unprepared-transform/unprepared_transform.cc",
         "1951-monitor-enter-no-suspend/raw_monitor.cc",
         "1953-pop-frame/pop_frame.cc",
+        "1957-error-ext/lasterror.cc",
     ],
     // Use NDK-compatible headers for ctstiagent.
     header_libs: [
diff --git a/test/knownfailures.json b/test/knownfailures.json
index f4f45ce..d831993 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1027,7 +1027,8 @@
                   "679-locks",
                   "999-redefine-hiddenapi",
                   "1000-non-moving-space-stress",
-                  "1951-monitor-enter-no-suspend"],
+                  "1951-monitor-enter-no-suspend",
+                  "1957-error-ext"],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]
     },