Merge changes Iba7e9207,I11a4d3cb,I5a6746d3
am: 2e53f8f69f

Change-Id: Id9471983dc5ca43cb269f5ad59a580e8487ac3ad
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 90b5def..b8ea597 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -196,6 +196,10 @@
   }
   bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  bool CanDeoptimize() {
+    return deoptimization_enabled_;
+  }
+
   // Executes everything with interpreter.
   void DeoptimizeEverything(const char* key)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 3c1311b..af77072 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -503,112 +503,112 @@
   }
 
   static jvmtiError GetLocalObject(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jobject* value_ptr ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jobject* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalInstance(jvmtiEnv* env,
-                                     jthread thread ATTRIBUTE_UNUSED,
-                                     jint depth ATTRIBUTE_UNUSED,
-                                     jobject* value_ptr ATTRIBUTE_UNUSED) {
+                                     jthread thread,
+                                     jint depth,
+                                     jobject* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalInstance(env, thread, depth, value_ptr);
   }
 
   static jvmtiError GetLocalInt(jvmtiEnv* env,
-                                jthread thread ATTRIBUTE_UNUSED,
-                                jint depth ATTRIBUTE_UNUSED,
-                                jint slot ATTRIBUTE_UNUSED,
-                                jint* value_ptr ATTRIBUTE_UNUSED) {
+                                jthread thread,
+                                jint depth,
+                                jint slot,
+                                jint* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalLong(jvmtiEnv* env,
-                                 jthread thread ATTRIBUTE_UNUSED,
-                                 jint depth ATTRIBUTE_UNUSED,
-                                 jint slot ATTRIBUTE_UNUSED,
-                                 jlong* value_ptr ATTRIBUTE_UNUSED) {
+                                 jthread thread,
+                                 jint depth,
+                                 jint slot,
+                                 jlong* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalFloat(jvmtiEnv* env,
-                                  jthread thread ATTRIBUTE_UNUSED,
-                                  jint depth ATTRIBUTE_UNUSED,
-                                  jint slot ATTRIBUTE_UNUSED,
-                                  jfloat* value_ptr ATTRIBUTE_UNUSED) {
+                                  jthread thread,
+                                  jint depth,
+                                  jint slot,
+                                  jfloat* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalDouble(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jdouble* value_ptr ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jdouble* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError SetLocalObject(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jobject value ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jobject value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalInt(jvmtiEnv* env,
-                                jthread thread ATTRIBUTE_UNUSED,
-                                jint depth ATTRIBUTE_UNUSED,
-                                jint slot ATTRIBUTE_UNUSED,
-                                jint value ATTRIBUTE_UNUSED) {
+                                jthread thread,
+                                jint depth,
+                                jint slot,
+                                jint value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalLong(jvmtiEnv* env,
-                                 jthread thread ATTRIBUTE_UNUSED,
-                                 jint depth ATTRIBUTE_UNUSED,
-                                 jint slot ATTRIBUTE_UNUSED,
-                                 jlong value ATTRIBUTE_UNUSED) {
+                                 jthread thread,
+                                 jint depth,
+                                 jint slot,
+                                 jlong value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalFloat(jvmtiEnv* env,
-                                  jthread thread ATTRIBUTE_UNUSED,
-                                  jint depth ATTRIBUTE_UNUSED,
-                                  jint slot ATTRIBUTE_UNUSED,
-                                  jfloat value ATTRIBUTE_UNUSED) {
+                                  jthread thread,
+                                  jint depth,
+                                  jint slot,
+                                  jfloat value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalDouble(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jdouble value ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jdouble value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
 
@@ -904,12 +904,12 @@
   }
 
   static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
-                                          jmethodID method ATTRIBUTE_UNUSED,
-                                          jint* entry_count_ptr ATTRIBUTE_UNUSED,
-                                          jvmtiLocalVariableEntry** table_ptr ATTRIBUTE_UNUSED) {
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLocalVariableEntry** table_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariableTable(env, method, entry_count_ptr, table_ptr);
   }
 
   static jvmtiError GetBytecodes(jvmtiEnv* env,
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 4d5bb95..a2259c7 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -204,6 +204,10 @@
 
 ALWAYS_INLINE
 static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, jvmtiError* error) {
+  if (src == nullptr) {
+    JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, 0, error);
+    return ret;
+  }
   size_t len = strlen(src) + 1;
   JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, len, error);
   if (ret != nullptr) {
@@ -227,7 +231,7 @@
     .can_get_source_file_name                        = 1,
     .can_get_line_numbers                            = 1,
     .can_get_source_debug_extension                  = 1,
-    .can_access_local_variables                      = 0,
+    .can_access_local_variables                      = 1,
     .can_maintain_original_method_order              = 0,
     .can_generate_single_step_events                 = 1,
     .can_generate_exception_events                   = 0,
diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h
index 43177ab..91e4055 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/runtime/openjdkjvmti/events-inl.h
@@ -414,9 +414,10 @@
                                            bool added) {
   ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable
                               : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
-  return caps.can_retransform_classes == 1 &&
-      IsEventEnabledAnywhere(event) &&
-      env->event_masks.IsEnabledAnywhere(event);
+  return (added && caps.can_access_local_variables == 1) ||
+      (caps.can_retransform_classes == 1 &&
+       IsEventEnabledAnywhere(event) &&
+       env->event_masks.IsEnabledAnywhere(event));
 }
 
 inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env,
@@ -428,6 +429,9 @@
       RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable);
       RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable);
     }
+    if (added && caps.can_access_local_variables == 1) {
+      HandleLocalAccessCapabilityAdded();
+    }
   }
 }
 
diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc
index 7a930d4..2944a45 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/runtime/openjdkjvmti/events.cc
@@ -610,6 +610,21 @@
   }
 }
 
+void EventHandler::HandleLocalAccessCapabilityAdded() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
+  art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
+  art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
+                                       art::gc::kGcCauseInstrumentation,
+                                       art::gc::kCollectorTypeInstrumentation);
+  art::ScopedSuspendAll ssa("Deoptimize everything for local variable access", true);
+  // TODO This should be disabled when there are no environments using it.
+  if (!instr->CanDeoptimize()) {
+    instr->EnableDeoptimization();
+  }
+  // TODO We should be able to support can_access_local_variables without this.
+  instr->DeoptimizeEverything("jvmti-local-variable-access");
+}
+
 // Handle special work for the given event type, if necessary.
 void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
   switch (event) {
diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h
index 5f37dcf..617519e 100644
--- a/runtime/openjdkjvmti/events.h
+++ b/runtime/openjdkjvmti/events.h
@@ -210,6 +210,7 @@
                                                            unsigned char** new_class_data) const;
 
   void HandleEventType(ArtJvmtiEvent event, bool enable);
+  void HandleLocalAccessCapabilityAdded();
 
   // List of all JvmTiEnv objects that have been created, in their creation order.
   // NB Some elements might be null representing envs that have been deleted. They should be skipped
diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc
index ab434d7..ed5645a 100644
--- a/runtime/openjdkjvmti/ti_method.cc
+++ b/runtime/openjdkjvmti/ti_method.cc
@@ -34,16 +34,22 @@
 #include "art_jvmti.h"
 #include "art_method-inl.h"
 #include "base/enums.h"
+#include "base/mutex-inl.h"
 #include "dex_file_annotations.h"
 #include "events-inl.h"
 #include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_loader.h"
+#include "mirror/object-inl.h"
 #include "mirror/object_array-inl.h"
 #include "modifiers.h"
 #include "nativehelper/ScopedLocalRef.h"
 #include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
+#include "stack.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
+#include "ti_thread.h"
 #include "ti_phase.h"
 
 namespace openjdkjvmti {
@@ -159,6 +165,106 @@
   return ERR(NONE);
 }
 
+jvmtiError MethodUtil::GetLocalVariableTable(jvmtiEnv* env,
+                                             jmethodID method,
+                                             jint* entry_count_ptr,
+                                             jvmtiLocalVariableEntry** table_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  const art::DexFile* dex_file = art_method->GetDexFile();
+  const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+  // TODO code_item == nullptr means that the method is abstract (or native, but we check that
+  // earlier). We should check what is returned by the RI in this situation since it's not clear
+  // what the appropriate return value is from the spec.
+  if (dex_file == nullptr || code_item == nullptr) {
+    return ERR(ABSENT_INFORMATION);
+  }
+
+  struct LocalVariableContext {
+    explicit LocalVariableContext(jvmtiEnv* jenv) : env_(jenv), variables_(), err_(OK) {}
+
+    static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+      reinterpret_cast<LocalVariableContext*>(raw_ctx)->Insert(entry);
+    }
+
+    void Insert(const art::DexFile::LocalInfo& entry) {
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> name_str = CopyString(env_, entry.name_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> sig_str = CopyString(env_, entry.descriptor_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> generic_sig_str = CopyString(env_, entry.signature_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      variables_.push_back({
+        .start_location = static_cast<jlocation>(entry.start_address_),
+        .length = static_cast<jint>(entry.end_address_ - entry.start_address_),
+        .name = name_str.release(),
+        .signature = sig_str.release(),
+        .generic_signature = generic_sig_str.release(),
+        .slot = entry.reg_,
+      });
+    }
+
+    jvmtiError Release(jint* out_entry_count_ptr, jvmtiLocalVariableEntry** out_table_ptr) {
+      jlong table_size = sizeof(jvmtiLocalVariableEntry) * variables_.size();
+      if (err_ != OK ||
+          (err_ = env_->Allocate(table_size,
+                                 reinterpret_cast<unsigned char**>(out_table_ptr))) != OK) {
+        Cleanup();
+        return err_;
+      } else {
+        *out_entry_count_ptr = variables_.size();
+        memcpy(*out_table_ptr, variables_.data(), table_size);
+        return OK;
+      }
+    }
+
+    void Cleanup() {
+      for (jvmtiLocalVariableEntry& e : variables_) {
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.name));
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.signature));
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.generic_signature));
+      }
+    }
+
+    jvmtiEnv* env_;
+    std::vector<jvmtiLocalVariableEntry> variables_;
+    jvmtiError err_;
+  };
+
+  LocalVariableContext context(env);
+  if (!dex_file->DecodeDebugLocalInfo(code_item,
+                                      art_method->IsStatic(),
+                                      art_method->GetDexMethodIndex(),
+                                      LocalVariableContext::Callback,
+                                      &context)) {
+    // Something went wrong with decoding the debug information. It might as well not be there.
+    return ERR(ABSENT_INFORMATION);
+  } else {
+    return context.Release(entry_count_ptr, table_ptr);
+  }
+}
+
 jvmtiError MethodUtil::GetMaxLocals(jvmtiEnv* env ATTRIBUTE_UNUSED,
                                     jmethodID method,
                                     jint* max_ptr) {
@@ -425,4 +531,541 @@
   return IsMethodT(env, m, test, is_synthetic_ptr);
 }
 
+struct FindFrameAtDepthVisitor : art::StackVisitor {
+ public:
+  FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        found_frame_(false),
+        cnt_(0),
+        depth_(static_cast<size_t>(depth)) { }
+
+  bool FoundFrame() {
+    return found_frame_;
+  }
+
+  bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
+    if (GetMethod()->IsRuntimeMethod()) {
+      return true;
+    }
+    if (cnt_ == depth_) {
+      // We found our frame, exit.
+      found_frame_ = true;
+      return false;
+    } else {
+      cnt_++;
+      return true;
+    }
+  }
+
+ private:
+  bool found_frame_;
+  size_t cnt_;
+  size_t depth_;
+};
+
+class CommonLocalVariableClosure : public art::Closure {
+ public:
+  CommonLocalVariableClosure(art::Thread* caller,
+                             jint depth,
+                             jint slot)
+      : result_(ERR(INTERNAL)), caller_(caller), depth_(depth), slot_(slot) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+    std::unique_ptr<art::Context> context(art::Context::Create());
+    FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+    visitor.WalkStack();
+    if (!visitor.FoundFrame()) {
+      // Must have been a bad depth.
+      result_ = ERR(NO_MORE_FRAMES);
+      return;
+    }
+    art::ArtMethod* method = visitor.GetMethod();
+    if (method->IsNative() || !visitor.IsShadowFrame()) {
+      // TODO We really should support get/set for non-shadow frames.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    } else if (method->GetCodeItem()->registers_size_ <= slot_) {
+      result_ = ERR(INVALID_SLOT);
+      return;
+    }
+    uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false);
+    if (pc == art::DexFile::kDexNoIndex) {
+      // Cannot figure out current PC.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    }
+    std::string descriptor;
+    art::Primitive::Type slot_type = art::Primitive::kPrimVoid;
+    jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type);
+    if (err != OK) {
+      result_ = err;
+      return;
+    }
+
+    err = GetTypeError(method, slot_type, descriptor);
+    if (err != OK) {
+      result_ = err;
+      return;
+    }
+    result_ = Execute(method, visitor);
+  }
+
+  jvmtiError GetResult() const {
+    return result_;
+  }
+
+ protected:
+  virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      REQUIRES(art::Locks::mutator_lock_) = 0;
+  virtual jvmtiError GetTypeError(art::ArtMethod* method,
+                                  art::Primitive::Type type,
+                                  const std::string& descriptor)
+      REQUIRES(art::Locks::mutator_lock_)  = 0;
+
+  jvmtiError GetSlotType(art::ArtMethod* method,
+                         uint32_t dex_pc,
+                         /*out*/std::string* descriptor,
+                         /*out*/art::Primitive::Type* type)
+      REQUIRES(art::Locks::mutator_lock_) {
+    const art::DexFile* dex_file = method->GetDexFile();
+    const art::DexFile::CodeItem* code_item = method->GetCodeItem();
+    if (dex_file == nullptr || code_item == nullptr) {
+      return ERR(OPAQUE_FRAME);
+    }
+
+    struct GetLocalVariableInfoContext {
+      explicit GetLocalVariableInfoContext(jint slot,
+                                          uint32_t pc,
+                                          std::string* out_descriptor,
+                                          art::Primitive::Type* out_type)
+          : found_(false), jslot_(slot), pc_(pc), descriptor_(out_descriptor), type_(out_type) {
+        *descriptor_ = "";
+        *type_ = art::Primitive::kPrimVoid;
+      }
+
+      static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+        reinterpret_cast<GetLocalVariableInfoContext*>(raw_ctx)->Handle(entry);
+      }
+
+      void Handle(const art::DexFile::LocalInfo& entry) {
+        if (found_) {
+          return;
+        } else if (entry.start_address_ <= pc_ &&
+                   entry.end_address_ > pc_ &&
+                   entry.reg_ == jslot_) {
+          found_ = true;
+          *type_ = art::Primitive::GetType(entry.descriptor_[0]);
+          *descriptor_ = entry.descriptor_;
+        }
+        return;
+      }
+
+      bool found_;
+      jint jslot_;
+      uint32_t pc_;
+      std::string* descriptor_;
+      art::Primitive::Type* type_;
+    };
+
+    GetLocalVariableInfoContext context(slot_, dex_pc, descriptor, type);
+    if (!dex_file->DecodeDebugLocalInfo(code_item,
+                                        method->IsStatic(),
+                                        method->GetDexMethodIndex(),
+                                        GetLocalVariableInfoContext::Callback,
+                                        &context) || !context.found_) {
+      // Something went wrong with decoding the debug information. It might as well not be there.
+      return ERR(INVALID_SLOT);
+    } else {
+      return OK;
+    }
+  }
+
+  jvmtiError result_;
+  art::Thread* caller_;
+  jint depth_;
+  jint slot_;
+};
+
+class GetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+  GetLocalVariableClosure(art::Thread* caller,
+                          jint depth,
+                          jint slot,
+                          art::Primitive::Type type,
+                          jvalue* val)
+      : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+  jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED,
+                          art::Primitive::Type slot_type,
+                          const std::string& descriptor ATTRIBUTE_UNUSED)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (slot_type) {
+      case art::Primitive::kPrimByte:
+      case art::Primitive::kPrimChar:
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimShort:
+      case art::Primitive::kPrimBoolean:
+        return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimLong:
+      case art::Primitive::kPrimFloat:
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimNot:
+        return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected primitive type " << slot_type;
+        UNREACHABLE();
+    }
+  }
+
+  jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (type_) {
+      case art::Primitive::kPrimNot: {
+        uint32_t ptr_val;
+        if (!visitor.GetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             art::kReferenceVReg,
+                             &ptr_val)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        art::ObjPtr<art::mirror::Object> obj(reinterpret_cast<art::mirror::Object*>(ptr_val));
+        val_->l = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+        break;
+      }
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimFloat: {
+        if (!visitor.GetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             type_ == art::Primitive::kPrimFloat ? art::kFloatVReg : art::kIntVReg,
+                             reinterpret_cast<uint32_t*>(&val_->i))) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimLong: {
+        auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+        auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+        if (!visitor.GetVRegPair(method,
+                                 static_cast<uint16_t>(slot_),
+                                 lo_type,
+                                 high_type,
+                                 reinterpret_cast<uint64_t*>(&val_->j))) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      default: {
+        LOG(FATAL) << "unexpected register type " << type_;
+        UNREACHABLE();
+      }
+    }
+    return OK;
+  }
+
+ private:
+  art::Primitive::Type type_;
+  jvalue* val_;
+};
+
+jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jthread thread,
+                                               jint depth,
+                                               jint slot,
+                                               art::Primitive::Type type,
+                                               jvalue* val) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  GetLocalVariableClosure c(self, depth, slot, type, val);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+class SetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+  SetLocalVariableClosure(art::Thread* caller,
+                          jint depth,
+                          jint slot,
+                          art::Primitive::Type type,
+                          jvalue val)
+      : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+  jvmtiError GetTypeError(art::ArtMethod* method,
+                          art::Primitive::Type slot_type,
+                          const std::string& descriptor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (slot_type) {
+      case art::Primitive::kPrimNot: {
+        if (type_ != art::Primitive::kPrimNot) {
+          return ERR(TYPE_MISMATCH);
+        } else if (val_.l == nullptr) {
+          return OK;
+        } else {
+          art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
+          art::ObjPtr<art::mirror::Class> set_class =
+              caller_->DecodeJObject(val_.l)->GetClass();
+          art::ObjPtr<art::mirror::ClassLoader> loader =
+              method->GetDeclaringClass()->GetClassLoader();
+          art::ObjPtr<art::mirror::Class> slot_class =
+              cl->LookupClass(caller_, descriptor.c_str(), loader);
+          DCHECK(!slot_class.IsNull());
+          return slot_class->IsAssignableFrom(set_class) ? OK : ERR(TYPE_MISMATCH);
+        }
+      }
+      case art::Primitive::kPrimByte:
+      case art::Primitive::kPrimChar:
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimShort:
+      case art::Primitive::kPrimBoolean:
+        return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimLong:
+      case art::Primitive::kPrimFloat:
+      case art::Primitive::kPrimDouble:
+        return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected primitive type " << slot_type;
+        UNREACHABLE();
+    }
+  }
+
+  jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (type_) {
+      case art::Primitive::kPrimNot: {
+        uint32_t ptr_val;
+        art::ObjPtr<art::mirror::Object> obj(caller_->DecodeJObject(val_.l));
+        ptr_val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(obj.Ptr()));
+        if (!visitor.SetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             ptr_val,
+                             art::kReferenceVReg)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimFloat: {
+        if (!visitor.SetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             static_cast<uint32_t>(val_.i),
+                             type_ == art::Primitive::kPrimFloat ? art::kFloatVReg
+                                                                 : art::kIntVReg)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimLong: {
+        auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+        auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+        if (!visitor.SetVRegPair(method,
+                                 static_cast<uint16_t>(slot_),
+                                 static_cast<uint64_t>(val_.j),
+                                 lo_type,
+                                 high_type)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      default: {
+        LOG(FATAL) << "unexpected register type " << type_;
+        UNREACHABLE();
+      }
+    }
+    return OK;
+  }
+
+ private:
+  art::Primitive::Type type_;
+  jvalue val_;
+};
+
+jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jthread thread,
+                                               jint depth,
+                                               jint slot,
+                                               art::Primitive::Type type,
+                                               jvalue val) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  SetLocalVariableClosure c(self, depth, slot, type, val);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+class GetLocalInstanceClosure : public art::Closure {
+ public:
+  GetLocalInstanceClosure(art::Thread* caller, jint depth, jobject* val)
+      : result_(ERR(INTERNAL)),
+        caller_(caller),
+        depth_(depth),
+        val_(val) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+    std::unique_ptr<art::Context> context(art::Context::Create());
+    FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+    visitor.WalkStack();
+    if (!visitor.FoundFrame()) {
+      // Must have been a bad depth.
+      result_ = ERR(NO_MORE_FRAMES);
+      return;
+    }
+    art::ArtMethod* method = visitor.GetMethod();
+    if (!visitor.IsShadowFrame() && !method->IsNative() && !method->IsProxyMethod()) {
+      // TODO We really should support get/set for non-shadow frames.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    }
+    result_ = OK;
+    art::ObjPtr<art::mirror::Object> obj = visitor.GetThisObject();
+    *val_ = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+  }
+
+  jvmtiError GetResult() const {
+    return result_;
+  }
+
+ private:
+  jvmtiError result_;
+  art::Thread* caller_;
+  jint depth_;
+  jobject* val_;
+};
+
+jvmtiError MethodUtil::GetLocalInstance(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jthread thread,
+                                        jint depth,
+                                        jobject* data) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  GetLocalInstanceClosure c(self, depth, data);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+#define FOR_JVMTI_JVALUE_TYPES(fn) \
+    fn(jint, art::Primitive::kPrimInt, i) \
+    fn(jlong, art::Primitive::kPrimLong, j) \
+    fn(jfloat, art::Primitive::kPrimFloat, f) \
+    fn(jdouble, art::Primitive::kPrimDouble, d) \
+    fn(jobject, art::Primitive::kPrimNot, l)
+
+namespace impl {
+
+template<typename T> void WriteJvalue(T, jvalue*);
+template<typename T> void ReadJvalue(jvalue, T*);
+template<typename T> art::Primitive::Type GetJNIType();
+
+#define JNI_TYPE_CHAR(type, prim, id) \
+template<> art::Primitive::Type GetJNIType<type>() { \
+  return prim; \
+}
+
+FOR_JVMTI_JVALUE_TYPES(JNI_TYPE_CHAR);
+
+#undef JNI_TYPE_CHAR
+
+#define RW_JVALUE(type, prim, id) \
+    template<> void ReadJvalue<type>(jvalue in, type* out) { \
+      *out = in.id; \
+    } \
+    template<> void WriteJvalue<type>(type in, jvalue* out) { \
+      out->id = in; \
+    }
+
+FOR_JVMTI_JVALUE_TYPES(RW_JVALUE);
+
+#undef RW_JVALUE
+
+}  // namespace impl
+
+template<typename T>
+jvmtiError MethodUtil::SetLocalVariable(jvmtiEnv* env,
+                                        jthread thread,
+                                        jint depth,
+                                        jint slot,
+                                        T data) {
+  jvalue v = {.j = 0};
+  art::Primitive::Type type = impl::GetJNIType<T>();
+  impl::WriteJvalue(data, &v);
+  return SetLocalVariableGeneric(env, thread, depth, slot, type, v);
+}
+
+template<typename T>
+jvmtiError MethodUtil::GetLocalVariable(jvmtiEnv* env,
+                                        jthread thread,
+                                        jint depth,
+                                        jint slot,
+                                        T* data) {
+  if (data == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  jvalue v = {.j = 0};
+  art::Primitive::Type type = impl::GetJNIType<T>();
+  jvmtiError err = GetLocalVariableGeneric(env, thread, depth, slot, type, &v);
+  if (err != OK) {
+    return err;
+  } else {
+    impl::ReadJvalue(v, data);
+    return OK;
+  }
+}
+
+#define GET_SET_LV(type, prim, id) \
+    template jvmtiError MethodUtil::GetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type*); \
+    template jvmtiError MethodUtil::SetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type);
+
+FOR_JVMTI_JVALUE_TYPES(GET_SET_LV);
+
+#undef GET_SET_LV
+
+#undef FOR_JVMTI_JVALUE_TYPES
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h
index d95a81b..aabaedb 100644
--- a/runtime/openjdkjvmti/ti_method.h
+++ b/runtime/openjdkjvmti/ti_method.h
@@ -34,6 +34,7 @@
 
 #include "jni.h"
 #include "jvmti.h"
+#include "primitive.h"
 
 namespace openjdkjvmti {
 
@@ -80,6 +81,32 @@
   static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr);
   static jvmtiError IsMethodObsolete(jvmtiEnv* env, jmethodID method, jboolean* is_obsolete_ptr);
   static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr);
+  static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLocalVariableEntry** table_ptr);
+
+  template<typename T>
+  static jvmtiError SetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T data);
+
+  template<typename T>
+  static jvmtiError GetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T* data);
+
+  static jvmtiError GetLocalInstance(jvmtiEnv* env, jthread thread, jint depth, jobject* data);
+
+ private:
+  static jvmtiError SetLocalVariableGeneric(jvmtiEnv* env,
+                                            jthread thread,
+                                            jint depth,
+                                            jint slot,
+                                            art::Primitive::Type type,
+                                            jvalue value);
+  static jvmtiError GetLocalVariableGeneric(jvmtiEnv* env,
+                                            jthread thread,
+                                            jint depth,
+                                            jint slot,
+                                            art::Primitive::Type type,
+                                            jvalue* value);
 };
 
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index 9acea2a..7d42879 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -171,9 +171,8 @@
 }
 
 // Get the native thread. The spec says a null object denotes the current thread.
-static art::Thread* GetNativeThread(jthread thread,
-                                    const art::ScopedObjectAccessAlreadyRunnable& soa)
-    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+art::Thread* ThreadUtil::GetNativeThread(jthread thread,
+                                         const art::ScopedObjectAccessAlreadyRunnable& soa) {
   if (thread == nullptr) {
     return art::Thread::Current();
   }
diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h
index 0f7e837..083bf8d 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/runtime/openjdkjvmti/ti_thread.h
@@ -35,10 +35,12 @@
 #include "jni.h"
 #include "jvmti.h"
 
+#include "base/macros.h"
 #include "base/mutex.h"
 
 namespace art {
 class ArtField;
+class ScopedObjectAccessAlreadyRunnable;
 class Thread;
 }  // namespace art
 
@@ -86,6 +88,10 @@
                                      const jthread* threads,
                                      jvmtiError* results);
 
+  static art::Thread* GetNativeThread(jthread thread,
+                                      const art::ScopedObjectAccessAlreadyRunnable& soa)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
  private:
   // We need to make sure only one thread tries to suspend threads at a time so we can get the
   // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a
diff --git a/test/1911-get-local-var-table/expected.txt b/test/1911-get-local-var-table/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/1911-get-local-var-table/expected.txt
diff --git a/test/1911-get-local-var-table/info.txt b/test/1911-get-local-var-table/info.txt
new file mode 100644
index 0000000..43955e5
--- /dev/null
+++ b/test/1911-get-local-var-table/info.txt
@@ -0,0 +1 @@
+Tests getting local variable table from JVMTI
diff --git a/test/1911-get-local-var-table/run b/test/1911-get-local-var-table/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1911-get-local-var-table/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1911-get-local-var-table/src/Main.java b/test/1911-get-local-var-table/src/Main.java
new file mode 100644
index 0000000..4e0c9e4
--- /dev/null
+++ b/test/1911-get-local-var-table/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.Test1911.run();
+  }
+}
diff --git a/test/1911-get-local-var-table/src/art/Breakpoint.java b/test/1911-get-local-var-table/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1911-get-local-var-table/src/art/Locals.java b/test/1911-get-local-var-table/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1911-get-local-var-table/src/art/Suspension.java b/test/1911-get-local-var-table/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1911-get-local-var-table/src/art/Test1911.java b/test/1911-get-local-var-table/src/art/Test1911.java
new file mode 100644
index 0000000..4dd9054
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Test1911.java
@@ -0,0 +1,218 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Test1911 {
+  // Class/dex file containing the following class.
+  //
+  // CLASS_BYTES generated with java version 1.8.0_45: javac -g art/Target.java
+  // DEX_BYTES generated with dx version 1.14: dx --dex --output=./classes.dex art/Target.class
+  //
+  // package art;
+  // import java.util.ArrayList;
+  // public class Target {
+  //   public int zzz;
+  //   public Target(int xxx) {
+  //     int q = xxx * 4;
+  //     zzz = q;
+  //   }
+  //   public static void doNothing(Object... objs) { doNothing(objs); }
+  //   public void doSomething(int x) {
+  //     doNothing(this);
+  //     int y = x + 3;
+  //     for (int z = 0; z < y * x; z++) {
+  //       float q = y - z;
+  //       double i = 0.3d * q;
+  //       doNothing(q, i);
+  //     }
+  //     Object o = new Object();
+  //     ArrayList<Integer> i = new ArrayList<>();
+  //     int p = 4 | x;
+  //     long q = 3 * p;
+  //     doNothing(p, q, o, i);
+  //   }
+  // }
+  public static byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQARgoABAAuCQANAC8KAA0AMAcAMQY/0zMzMzMzMwoAMgAzCgA0ADUHADYKAAkALgoA" +
+    "NwA4CgA5ADoHADsBAAN6enoBAAFJAQAGPGluaXQ+AQAEKEkpVgEABENvZGUBAA9MaW5lTnVtYmVy" +
+    "VGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAMTGFydC9UYXJnZXQ7AQADeHh4AQAB" +
+    "cQEACWRvTm90aGluZwEAFihbTGphdmEvbGFuZy9PYmplY3Q7KVYBAARvYmpzAQATW0xqYXZhL2xh" +
+    "bmcvT2JqZWN0OwEAC2RvU29tZXRoaW5nAQABRgEAAWkBAAFEAQABegEAAXgBAAF5AQABbwEAEkxq" +
+    "YXZhL2xhbmcvT2JqZWN0OwEAFUxqYXZhL3V0aWwvQXJyYXlMaXN0OwEAAXABAAFKAQAWTG9jYWxW" +
+    "YXJpYWJsZVR5cGVUYWJsZQEAKkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdl" +
+    "cjs+OwEADVN0YWNrTWFwVGFibGUBAApTb3VyY2VGaWxlAQALVGFyZ2V0LmphdmEMABAAPAwADgAP" +
+    "DAAZABoBABBqYXZhL2xhbmcvT2JqZWN0BwA9DAA+AD8HAEAMAD4AQQEAE2phdmEvdXRpbC9BcnJh" +
+    "eUxpc3QHAEIMAD4AQwcARAwAPgBFAQAKYXJ0L1RhcmdldAEAAygpVgEAD2phdmEvbGFuZy9GbG9h" +
+    "dAEAB3ZhbHVlT2YBABQoRilMamF2YS9sYW5nL0Zsb2F0OwEAEGphdmEvbGFuZy9Eb3VibGUBABUo" +
+    "RClMamF2YS9sYW5nL0RvdWJsZTsBABFqYXZhL2xhbmcvSW50ZWdlcgEAFihJKUxqYXZhL2xhbmcv" +
+    "SW50ZWdlcjsBAA5qYXZhL2xhbmcvTG9uZwEAEyhKKUxqYXZhL2xhbmcvTG9uZzsAIQANAAQAAAAB" +
+    "AAEADgAPAAAAAwABABAAEQABABIAAABYAAIAAwAAAA4qtwABGwdoPSoctQACsQAAAAIAEwAAABIA" +
+    "BAAAAAUABAAGAAgABwANAAgAFAAAACAAAwAAAA4AFQAWAAAAAAAOABcADwABAAgABgAYAA8AAgCJ" +
+    "ABkAGgABABIAAAAvAAEAAQAAAAUquAADsQAAAAIAEwAAAAYAAQAAAAkAFAAAAAwAAQAAAAUAGwAc" +
+    "AAAAAQAdABEAAQASAAABWAAFAAgAAACCBL0ABFkDKlO4AAMbBmA9Az4dHBtoogAvHB1khjgEFAAF" +
+    "FwSNazkFBb0ABFkDFwS4AAdTWQQYBbgACFO4AAOEAwGn/9C7AARZtwABTrsACVm3AAo6BAcbgDYF" +
+    "BhUFaIU3Bge9AARZAxUFuAALU1kEFga4AAxTWQUtU1kGGQRTuAADsQAAAAQAEwAAADYADQAAAAsA" +
+    "CwAMAA8ADQAYAA4AHgAPACcAEAA+AA0ARAASAEwAEwBVABQAWgAVAGEAFgCBABcAFAAAAGYACgAe" +
+    "ACAAGAAeAAQAJwAXAB8AIAAFABEAMwAhAA8AAwAAAIIAFQAWAAAAAACCACIADwABAA8AcwAjAA8A" +
+    "AgBMADYAJAAlAAMAVQAtAB8AJgAEAFoAKAAnAA8ABQBhACEAGAAoAAYAKQAAAAwAAQBVAC0AHwAq" +
+    "AAQAKwAAAAoAAv0AEQEB+gAyAAEALAAAAAIALQ==");
+  public static byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQCQtgjEV631Ma/btYyIy2IzqHWNN+nZiwl0BQAAcAAAAHhWNBIAAAAAAAAAANQEAAAk" +
+    "AAAAcAAAAA0AAAAAAQAABwAAADQBAAABAAAAiAEAAAkAAACQAQAAAQAAANgBAAB8AwAA+AEAAB4D" +
+    "AAAmAwAAKQMAACwDAAAvAwAAMgMAADYDAAA6AwAAPgMAAEIDAABQAwAAZAMAAHcDAACMAwAAngMA" +
+    "ALIDAADJAwAA9QMAAAIEAAAFBAAACQQAAA0EAAAiBAAALQQAADoEAAA9BAAAQAQAAEYEAABJBAAA" +
+    "TAQAAFIEAABbBAAAXgQAAGMEAABmBAAAaQQAAAEAAAACAAAAAwAAAAQAAAAJAAAACgAAAAsAAAAM" +
+    "AAAADQAAAA4AAAAPAAAAEgAAABUAAAAFAAAABQAAAPgCAAAGAAAABgAAAAADAAAHAAAABwAAAAgD" +
+    "AAAIAAAACAAAABADAAASAAAACwAAAAAAAAATAAAACwAAAAgDAAAUAAAACwAAABgDAAAEAAIAIwAA" +
+    "AAQABQAAAAAABAAGABYAAAAEAAUAFwAAAAUAAAAeAAAABgABAB4AAAAHAAIAHgAAAAgAAwAeAAAA" +
+    "CQAEAAAAAAAKAAQAAAAAAAQAAAABAAAACQAAAAAAAAARAAAAAAAAAL4EAAAAAAAAAwACAAEAAABu" +
+    "BAAACAAAAHAQBwABANoAAgRZEAAADgABAAEAAQAAAHsEAAAEAAAAcRABAAAADgAQAAIAAgAAAIEE" +
+    "AABcAAAAEhkjmQwAEgpNDgkKcRABAAkA2AUPAxIIkgkFDzWYJACRCQUIgpYYCjMzMzMzM9M/iWyt" +
+    "AAoMEikjmQwAEgpxEAQABgAMC00LCQoSGnEgAwAQAAwLTQsJCnEQAQAJANgICAEo2yIDCQBwEAcA" +
+    "AwAiAgoAcBAIAAIA3gQPBNoJBAOBlhJJI5kMABIKcRAFAAQADAtNCwkKEhpxIAYAdgAMC00LCQoS" +
+    "Kk0DCQoSOk0CCQpxEAEACQAOAAEAAAAAAAAAAQAAAAEAAAABAAAAAgAAAAEAAAADAAAAAQAAAAwA" +
+    "Bjxpbml0PgABRAABRgABSQABSgACTEQAAkxGAAJMSQACTEoADExhcnQvVGFyZ2V0OwASTGphdmEv" +
+    "bGFuZy9Eb3VibGU7ABFMamF2YS9sYW5nL0Zsb2F0OwATTGphdmEvbGFuZy9JbnRlZ2VyOwAQTGph" +
+    "dmEvbGFuZy9Mb25nOwASTGphdmEvbGFuZy9PYmplY3Q7ABVMamF2YS91dGlsL0FycmF5TGlzdDsA" +
+    "KkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdlcjs+OwALVGFyZ2V0LmphdmEA" +
+    "AVYAAlZJAAJWTAATW0xqYXZhL2xhbmcvT2JqZWN0OwAJZG9Ob3RoaW5nAAtkb1NvbWV0aGluZwAB" +
+    "aQABbwAEb2JqcwABcAABcQAEdGhpcwAHdmFsdWVPZgABeAADeHh4AAF5AAF6AAN6enoABQEhBw48" +
+    "LQMAHQMtAAkBGwcOAAsBIAcOli0DBSIDAQEDCCMDSzwDBh0ChwMAGQEBFAtABQAFBloDAxoKWgQC" +
+    "GQsRLQMEHAM8AwYdBAEaDwAAAQIBAAEAgYAE+AMBiQGYBAIBsAQADQAAAAAAAAABAAAAAAAAAAEA" +
+    "AAAkAAAAcAAAAAIAAAANAAAAAAEAAAMAAAAHAAAANAEAAAQAAAABAAAAiAEAAAUAAAAJAAAAkAEA" +
+    "AAYAAAABAAAA2AEAAAEgAAADAAAA+AEAAAEQAAAFAAAA+AIAAAIgAAAkAAAAHgMAAAMgAAADAAAA" +
+    "bgQAAAAgAAABAAAAvgQAAAAQAAABAAAA1AQAAA==");
+
+
+  // The variables of the functions in the above Target class.
+  public static Set<Locals.VariableDescription>[] CONSTRUCTOR_VARIABLES = new Set[] {
+      // RI Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(8, 6, "q", "I", null, 2),
+              new Locals.VariableDescription(0, 14, "xxx", "I", null, 1),
+              new Locals.VariableDescription(0, 14, "this", "Lart/Target;", null, 0))),
+      // ART Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 8, "this", "Lart/Target;", null, 1),
+              new Locals.VariableDescription(5, 3, "q", "I", null, 0),
+              new Locals.VariableDescription(0, 8, "xxx", "I", null, 2))),
+  };
+
+  public static Set<Locals.VariableDescription>[] DO_NOTHING_VARIABLES = new Set[] {
+      // RI Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 5, "objs", "[Ljava/lang/Object;", null, 0))),
+      // ART Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 4, "objs", "[Ljava/lang/Object;", null, 0))),
+  };
+
+  public static Set<Locals.VariableDescription>[] DO_SOMETHING_VARIABLES = new Set[] {
+      // RI Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 130, "x", "I", null, 1),
+              new Locals.VariableDescription(76, 54, "o", "Ljava/lang/Object;", null, 3),
+              new Locals.VariableDescription(30, 32, "q", "F", null, 4),
+              new Locals.VariableDescription(39, 23, "i", "D", null, 5),
+              new Locals.VariableDescription(17, 51, "z", "I", null, 3),
+              new Locals.VariableDescription(15, 115, "y", "I", null, 2),
+              new Locals.VariableDescription(90, 40, "p", "I", null, 5),
+              new Locals.VariableDescription(97, 33, "q", "J", null, 6),
+              new Locals.VariableDescription(0, 130, "this", "Lart/Target;", null, 0),
+              new Locals.VariableDescription(85,
+                                             45,
+                                             "i",
+                                             "Ljava/util/ArrayList;",
+                                             "Ljava/util/ArrayList<Ljava/lang/Integer;>;",
+                                             4))),
+      // ART Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(19, 31, "q", "F", null, 6),
+              new Locals.VariableDescription(55, 37, "o", "Ljava/lang/Object;", null, 3),
+              new Locals.VariableDescription(0, 92, "this", "Lart/Target;", null, 14),
+              new Locals.VariableDescription(12, 80, "z", "I", null, 8),
+              new Locals.VariableDescription(11, 81, "y", "I", null, 5),
+              new Locals.VariableDescription(62, 30, "p", "I", null, 4),
+              new Locals.VariableDescription(0, 92, "x", "I", null, 15),
+              new Locals.VariableDescription(27, 23, "i", "D", null, 0),
+              new Locals.VariableDescription(65, 27, "q", "J", null, 6),
+              new Locals.VariableDescription(60,
+                                             32,
+                                             "i",
+                                             "Ljava/util/ArrayList;",
+                                             "Ljava/util/ArrayList<Ljava/lang/Integer;>;",
+                                             2))),
+  };
+
+  // Get a classloader that can load the Target class.
+  public static ClassLoader getClassLoader() throws Exception {
+    try {
+      Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader");
+      Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class);
+      // We are on art since we got the InMemoryDexClassLoader.
+      return (ClassLoader)ctor.newInstance(
+          ByteBuffer.wrap(DEX_BYTES), Test1911.class.getClassLoader());
+    } catch (ClassNotFoundException e) {
+      // Running on RI.
+      return new ClassLoader(Test1911.class.getClassLoader()) {
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+          if (name.equals("art.Target")) {
+            return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length);
+          } else {
+            return super.findClass(name);
+          }
+        }
+      };
+    }
+  }
+
+  public static void CheckLocalVariableTable(Executable m,
+          Set<Locals.VariableDescription>[] possible_vars) {
+    Set<Locals.VariableDescription> real_vars =
+            new HashSet<>(Arrays.asList(Locals.GetLocalVariableTable(m)));
+    for (Set<Locals.VariableDescription> pos : possible_vars) {
+      if (pos.equals(real_vars)) {
+        return;
+      }
+    }
+    System.out.println("Unexpected variables for " + m);
+    System.out.println("Received: " + real_vars);
+    System.out.println("Expected one of:");
+    for (Object pos : possible_vars) {
+      System.out.println("\t" + pos);
+    }
+  }
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    Class<?> target = getClassLoader().loadClass("art.Target");
+    CheckLocalVariableTable(target.getDeclaredConstructor(Integer.TYPE),
+            CONSTRUCTOR_VARIABLES);
+    CheckLocalVariableTable(target.getDeclaredMethod("doNothing", (new Object[0]).getClass()),
+            DO_NOTHING_VARIABLES);
+    CheckLocalVariableTable(target.getDeclaredMethod("doSomething", Integer.TYPE),
+            DO_SOMETHING_VARIABLES);
+  }
+}
+
diff --git a/test/1912-get-set-local-primitive/expected.txt b/test/1912-get-set-local-primitive/expected.txt
new file mode 100644
index 0000000..f2c5ce8
--- /dev/null
+++ b/test/1912-get-set-local-primitive/expected.txt
@@ -0,0 +1,108 @@
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) got value: 42
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) set value: 2147483647
+	Value is '2147483647' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) got value: 9001
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) set value: 9223372036854775807
+	Value is '9223372036854775807' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) got value: 1.618
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) set value: 9.2
+	Value is '9.2' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) got value: 3.1415
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) set value: 12.4
+	Value is '12.4' (class: class java.lang.Double)
+Running public static void art.Test1912.BooleanMethod(java.lang.Runnable) with "SetIntBoolSize" on remote thread.
+"SetIntBoolSize" on public static void art.Test1912.BooleanMethod(java.lang.Runnable) set value: 1
+	Value is 'true' (class: class java.lang.Boolean)
+Running public static void art.Test1912.ByteMethod(java.lang.Runnable) with "SetIntByteSize" on remote thread.
+"SetIntByteSize" on public static void art.Test1912.ByteMethod(java.lang.Runnable) set value: 126
+	Value is '126' (class: class java.lang.Byte)
+Running public static void art.Test1912.CharMethod(java.lang.Runnable) with "SetIntCharSize" on remote thread.
+"SetIntCharSize" on public static void art.Test1912.CharMethod(java.lang.Runnable) set value: 65534
+	Value is '<Char: -1>' (class: class java.lang.String)
+Running public static void art.Test1912.ShortMethod(java.lang.Runnable) with "SetIntShortSize" on remote thread.
+"SetIntShortSize" on public static void art.Test1912.ShortMethod(java.lang.Runnable) set value: 32766
+	Value is '32766' (class: class java.lang.Short)
diff --git a/test/1912-get-set-local-primitive/info.txt b/test/1912-get-set-local-primitive/info.txt
new file mode 100644
index 0000000..87a7b35
--- /dev/null
+++ b/test/1912-get-set-local-primitive/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get/set Local variable primitives.
+
diff --git a/test/1912-get-set-local-primitive/run b/test/1912-get-set-local-primitive/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1912-get-set-local-primitive/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1912-get-set-local-primitive/src/Main.java b/test/1912-get-set-local-primitive/src/Main.java
new file mode 100644
index 0000000..9ff69f8
--- /dev/null
+++ b/test/1912-get-set-local-primitive/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.Test1912.run();
+  }
+}
diff --git a/test/1912-get-set-local-primitive/src/art/Breakpoint.java b/test/1912-get-set-local-primitive/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1912-get-set-local-primitive/src/art/Locals.java b/test/1912-get-set-local-primitive/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1912-get-set-local-primitive/src/art/StackTrace.java b/test/1912-get-set-local-primitive/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1912-get-set-local-primitive/src/art/Suspension.java b/test/1912-get-set-local-primitive/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1912-get-set-local-primitive/src/art/Test1912.java b/test/1912-get-set-local-primitive/src/art/Test1912.java
new file mode 100644
index 0000000..24149f4
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Test1912.java
@@ -0,0 +1,260 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+// TODO Rename test to set-get-local-prim
+
+public class Test1912 {
+  public static final String TARGET_VAR = "TARGET";
+
+
+  public static void reportValue(Object val) {
+    if (val instanceof Character) {
+      val = "<Char: " + Character.getNumericValue(((Character)val).charValue()) + ">";
+    }
+    System.out.println("\tValue is '" + val + "' (class: " + val.getClass() + ")");
+  }
+
+  public static void BooleanMethod(Runnable safepoint) {
+    boolean TARGET = false;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void ByteMethod(Runnable safepoint) {
+    byte TARGET = 8;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void CharMethod(Runnable safepoint) {
+    char TARGET = 'q';
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void ShortMethod(Runnable safepoint) {
+    short TARGET = 321;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void IntMethod(Runnable safepoint) {
+    int TARGET = 42;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void LongMethod(Runnable safepoint) {
+    long TARGET = 9001;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void FloatMethod(Runnable safepoint) {
+    float TARGET = 1.618f;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void DoubleMethod(Runnable safepoint) {
+    double TARGET = 3.1415d;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static interface SafepointFunction {
+    public void invoke(
+        Thread thread,
+        Method target,
+        Locals.VariableDescription TARGET_desc,
+        int depth) throws Exception;
+  }
+
+  public static interface SetterFunction {
+    public void SetVar(Thread t, int depth, int slot, Object v);
+  }
+
+  public static interface GetterFunction {
+    public Object GetVar(Thread t, int depth, int slot);
+  }
+
+  public static SafepointFunction NamedSet(
+      final String type, final SetterFunction get, final Object v) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          get.SetVar(t, depth, desc.slot, v);
+          System.out.println(this + " on " + method + " set value: " + v);
+        } catch (Exception e) {
+          System.out.println(
+              this + " on " + method + " failed to set value " + v + " due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Set" + type + "\"";
+      }
+    };
+  }
+
+  public static SafepointFunction NamedGet(final String type, final GetterFunction get) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          Object res = get.GetVar(t, depth, desc.slot);
+          System.out.println(this + " on " + method + " got value: " + res);
+        } catch (Exception e) {
+          System.out.println(this + " on " + method + " failed due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Get" + type + "\"";
+      }
+    };
+  }
+
+  public static class TestCase {
+    public final Method target;
+
+    public TestCase(Method target) {
+      this.target = target;
+    }
+
+    public static class ThreadPauser implements Runnable {
+      public final Semaphore sem_wakeup_main;
+      public final Semaphore sem_wait;
+
+      public ThreadPauser() {
+        sem_wakeup_main = new Semaphore(0);
+        sem_wait = new Semaphore(0);
+      }
+
+      public void run() {
+        try {
+          sem_wakeup_main.release();
+          sem_wait.acquire();
+        } catch (Exception e) {
+          throw new Error("Error with semaphores!", e);
+        }
+      }
+
+      public void waitForOtherThreadToPause() throws Exception {
+        sem_wakeup_main.acquire();
+      }
+
+      public void wakeupOtherThread() throws Exception {
+        sem_wait.release();
+      }
+    }
+
+    public void exec(final SafepointFunction safepoint) throws Exception {
+      System.out.println("Running " + target + " with " + safepoint + " on remote thread.");
+      final ThreadPauser pause = new ThreadPauser();
+      Thread remote = new Thread(
+          () -> {
+            try {
+              target.invoke(null, pause);
+            } catch (Exception e) {
+              throw new Error("Error invoking remote thread " + Thread.currentThread(), e);
+            }
+          },
+          "remote thread for " + target + " with " + safepoint);
+      remote.start();
+      pause.waitForOtherThreadToPause();
+      try {
+        Suspension.suspend(remote);
+        StackTrace.StackFrameData frame = findStackFrame(remote);
+        Locals.VariableDescription desc = findTargetVar(frame.current_location);
+        safepoint.invoke(remote, target, desc, frame.depth);
+      } finally {
+        Suspension.resume(remote);
+        pause.wakeupOtherThread();
+        remote.join();
+      }
+    }
+
+    private Locals.VariableDescription findTargetVar(long loc) {
+      for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) {
+        if (var.start_location <= loc &&
+            var.length + var.start_location > loc &&
+            var.name.equals(TARGET_VAR)) {
+          return var;
+        }
+      }
+      throw new Error(
+          "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc);
+    }
+
+    private StackTrace.StackFrameData findStackFrame(Thread thr) {
+      for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+        if (frame.method.equals(target)) {
+          return frame;
+        }
+      }
+      throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+    }
+  }
+  public static Method getMethod(String name) throws Exception {
+    return Test1912.class.getDeclaredMethod(name, Runnable.class);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final TestCase[] MAIN_TEST_CASES = new TestCase[] {
+      new TestCase(getMethod("IntMethod")),
+      new TestCase(getMethod("LongMethod")),
+      new TestCase(getMethod("FloatMethod")),
+      new TestCase(getMethod("DoubleMethod")),
+    };
+
+    final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] {
+      NamedGet("Int", Locals::GetLocalVariableInt),
+      NamedGet("Long", Locals::GetLocalVariableLong),
+      NamedGet("Float", Locals::GetLocalVariableFloat),
+      NamedGet("Double", Locals::GetLocalVariableDouble),
+      NamedSet("Int", Locals::SetLocalVariableInt, Integer.MAX_VALUE),
+      NamedSet("Long", Locals::SetLocalVariableLong, Long.MAX_VALUE),
+      NamedSet("Float", Locals::SetLocalVariableFloat, 9.2f),
+      NamedSet("Double", Locals::SetLocalVariableDouble, 12.4d),
+    };
+
+    for (TestCase t: MAIN_TEST_CASES) {
+      for (SafepointFunction s : SAFEPOINTS) {
+        t.exec(s);
+      }
+    }
+
+    // Test int for small values.
+    new TestCase(getMethod("BooleanMethod")).exec(
+        NamedSet("IntBoolSize", Locals::SetLocalVariableInt, 1));
+    new TestCase(getMethod("ByteMethod")).exec(
+      NamedSet("IntByteSize", Locals::SetLocalVariableInt, Byte.MAX_VALUE - 1));
+
+    new TestCase(getMethod("CharMethod")).exec(
+      NamedSet("IntCharSize", Locals::SetLocalVariableInt, Character.MAX_VALUE - 1));
+    new TestCase(getMethod("ShortMethod")).exec(
+      NamedSet("IntShortSize", Locals::SetLocalVariableInt, Short.MAX_VALUE - 1));
+  }
+}
+
diff --git a/test/1913-get-set-local-objects/expected.txt b/test/1913-get-set-local-objects/expected.txt
new file mode 100644
index 0000000..23f4992
--- /dev/null
+++ b/test/1913-get-set-local-objects/expected.txt
@@ -0,0 +1,72 @@
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) got value: TestClass1("ObjectMethod")
+	Value is 'TestClass1("ObjectMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: null
+	Value is 'null' (class: NULL)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1")
+	Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")")
+	Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2("Set TestClass2")
+	Value is 'TestClass2("Set TestClass2")' (class: class art.Test1913$TestClass2)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")")
+	Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) got value: TestClass1("InterfaceMethod")
+	Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: null
+	Value is 'null' (class: NULL)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1")
+	Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")")
+	Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")")
+	Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) got value: TestClass1("SpecificClassMethod")
+	Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: null
+	Value is 'null' (class: NULL)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1")
+	Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")")
+	Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value null due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1("Set TestClass1") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1ext("TestClass1("Set TestClass1ext")") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
diff --git a/test/1913-get-set-local-objects/info.txt b/test/1913-get-set-local-objects/info.txt
new file mode 100644
index 0000000..86ac743
--- /dev/null
+++ b/test/1913-get-set-local-objects/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get and set local variable object.
+
diff --git a/test/1913-get-set-local-objects/run b/test/1913-get-set-local-objects/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1913-get-set-local-objects/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1913-get-set-local-objects/src/Main.java b/test/1913-get-set-local-objects/src/Main.java
new file mode 100644
index 0000000..45565c2
--- /dev/null
+++ b/test/1913-get-set-local-objects/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.Test1913.run();
+  }
+}
diff --git a/test/1913-get-set-local-objects/src/art/Breakpoint.java b/test/1913-get-set-local-objects/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1913-get-set-local-objects/src/art/Locals.java b/test/1913-get-set-local-objects/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1913-get-set-local-objects/src/art/StackTrace.java b/test/1913-get-set-local-objects/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1913-get-set-local-objects/src/art/Suspension.java b/test/1913-get-set-local-objects/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1913-get-set-local-objects/src/art/Test1913.java b/test/1913-get-set-local-objects/src/art/Test1913.java
new file mode 100644
index 0000000..417138a
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Test1913.java
@@ -0,0 +1,251 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1913 {
+  public static final String TARGET_VAR = "TARGET";
+
+  public static interface TestInterface {
+    public default void doNothing() {}
+  }
+  public static class TestClass1 implements TestInterface {
+    public String id;
+    public TestClass1(String id) { this.id = id; }
+    public String toString() { return String.format("TestClass1(\"%s\")", id); }
+  }
+
+  public static class TestClass1ext extends TestClass1 {
+    public TestClass1ext(String id) { super(id); }
+    public String toString() { return String.format("TestClass1ext(\"%s\")", super.toString()); }
+  }
+  public static class TestClass2 {
+    public String id;
+    public TestClass2(String id) { this.id = id; }
+    public String toString() { return String.format("TestClass2(\"%s\")", id); }
+  }
+  public static class TestClass2impl extends TestClass2 implements TestInterface {
+    public TestClass2impl(String id) { super(id); }
+    public String toString() { return String.format("TestClass2impl(\"%s\")", super.toString()); }
+  }
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "' (class: "
+        + (val != null ? val.getClass() : "NULL") + ")");
+  }
+
+  public static void PrimitiveMethod(Runnable safepoint) {
+    int TARGET = 42;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  // b/64115302: Needed to make sure that DX doesn't change the type of TARGET to TestClass1.
+  private static Object AsObject(Object o) { return o; }
+  public static void ObjectMethod(Runnable safepoint) {
+    Object TARGET = AsObject(new TestClass1("ObjectMethod"));
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static void InterfaceMethod(Runnable safepoint) {
+    TestInterface TARGET = new TestClass1("InterfaceMethod");
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static void SpecificClassMethod(Runnable safepoint) {
+    TestClass1 TARGET = new TestClass1("SpecificClassMethod");
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static interface SafepointFunction {
+    public void invoke(
+        Thread thread,
+        Method target,
+        Locals.VariableDescription TARGET_desc,
+        int depth) throws Exception;
+  }
+
+  public static interface SetterFunction {
+    public void SetVar(Thread t, int depth, int slot, Object v);
+  }
+
+  public static interface GetterFunction {
+    public Object GetVar(Thread t, int depth, int slot);
+  }
+
+  public static SafepointFunction NamedSet(
+      final String type, final SetterFunction get, final Object v) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          get.SetVar(t, depth, desc.slot, v);
+          System.out.println(this + " on " + method + " set value: " + v);
+        } catch (Exception e) {
+          System.out.println(
+              this + " on " + method + " failed to set value " + v + " due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Set" + type + "\"";
+      }
+    };
+  }
+
+  public static SafepointFunction NamedGet(final String type, final GetterFunction get) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          Object res = get.GetVar(t, depth, desc.slot);
+          System.out.println(this + " on " + method + " got value: " + res);
+        } catch (Exception e) {
+          System.out.println(this + " on " + method + " failed due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Get" + type + "\"";
+      }
+    };
+  }
+
+  public static class TestCase {
+    public final Method target;
+
+    public TestCase(Method target) {
+      this.target = target;
+    }
+
+    public static class ThreadPauser implements Runnable {
+      public final Semaphore sem_wakeup_main;
+      public final Semaphore sem_wait;
+
+      public ThreadPauser() {
+        sem_wakeup_main = new Semaphore(0);
+        sem_wait = new Semaphore(0);
+      }
+
+      public void run() {
+        try {
+          sem_wakeup_main.release();
+          sem_wait.acquire();
+        } catch (Exception e) {
+          throw new Error("Error with semaphores!", e);
+        }
+      }
+
+      public void waitForOtherThreadToPause() throws Exception {
+        sem_wakeup_main.acquire();
+      }
+
+      public void wakeupOtherThread() throws Exception {
+        sem_wait.release();
+      }
+    }
+
+    public void exec(final SafepointFunction safepoint) throws Exception {
+      System.out.println("Running " + target + " with " + safepoint + " on remote thread.");
+      final ThreadPauser pause = new ThreadPauser();
+      Thread remote = new Thread(
+          () -> {
+            try {
+              target.invoke(null, pause);
+            } catch (Exception e) {
+              throw new Error("Error invoking remote thread " + Thread.currentThread(), e);
+            }
+          },
+          "remote thread for " + target + " with " + safepoint);
+      remote.start();
+      pause.waitForOtherThreadToPause();
+      try {
+        Suspension.suspend(remote);
+        StackTrace.StackFrameData frame = findStackFrame(remote);
+        Locals.VariableDescription desc = findTargetVar(frame.current_location);
+        safepoint.invoke(remote, target, desc, frame.depth);
+      } finally {
+        Suspension.resume(remote);
+        pause.wakeupOtherThread();
+        remote.join();
+      }
+    }
+
+    private Locals.VariableDescription findTargetVar(long loc) {
+      for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) {
+        if (var.start_location <= loc &&
+            var.length + var.start_location > loc &&
+            var.name.equals(TARGET_VAR)) {
+          return var;
+        }
+      }
+      throw new Error(
+          "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc);
+    }
+
+    private StackTrace.StackFrameData findStackFrame(Thread thr) {
+      for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+        if (frame.method.equals(target)) {
+          return frame;
+        }
+      }
+      throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+    }
+  }
+  public static Method getMethod(String name) throws Exception {
+    return Test1913.class.getDeclaredMethod(name, Runnable.class);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final TestCase[] MAIN_TEST_CASES = new TestCase[] {
+      new TestCase(getMethod("ObjectMethod")),
+      new TestCase(getMethod("InterfaceMethod")),
+      new TestCase(getMethod("SpecificClassMethod")),
+      new TestCase(getMethod("PrimitiveMethod")),
+    };
+
+    final SetterFunction set_obj = Locals::SetLocalVariableObject;
+    final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] {
+      NamedGet("GetObject",      Locals::GetLocalVariableObject),
+      NamedSet("Null",           set_obj, null),
+      NamedSet("TestClass1",     set_obj, new TestClass1("Set TestClass1")),
+      NamedSet("TestClass1ext",  set_obj, new TestClass1ext("Set TestClass1ext")),
+      NamedSet("TestClass2",     set_obj, new TestClass2("Set TestClass2")),
+      NamedSet("TestClass2impl", set_obj, new TestClass2impl("Set TestClass2impl")),
+    };
+
+    for (TestCase t: MAIN_TEST_CASES) {
+      for (SafepointFunction s : SAFEPOINTS) {
+        t.exec(s);
+      }
+    }
+  }
+}
+
diff --git a/test/1914-get-local-instance/expected.txt b/test/1914-get-local-instance/expected.txt
new file mode 100644
index 0000000..4117942
--- /dev/null
+++ b/test/1914-get-local-instance/expected.txt
@@ -0,0 +1,12 @@
+Running public static void art.Test1914.StaticMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public static void art.Test1914.StaticMethod(java.lang.Runnable) got value: null
+	Value is 'null' (class: NULL)
+Running public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) got value: null
+	Value is 'null' (class: NULL)
+Running public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) got value: TargetClass("InstanceMethodObject")
+	Value is 'TargetClass("InstanceMethodObject")' (class: class art.Test1914$TargetClass)
+Running public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) got value: TargetClass("NativeInstanceMethodObject")
+	Value is 'TargetClass("NativeInstanceMethodObject")' (class: class art.Test1914$TargetClass)
diff --git a/test/1914-get-local-instance/info.txt b/test/1914-get-local-instance/info.txt
new file mode 100644
index 0000000..9fc3d62
--- /dev/null
+++ b/test/1914-get-local-instance/info.txt
@@ -0,0 +1,2 @@
+Test for jvmti get local instance
+
diff --git a/test/1914-get-local-instance/local_instance.cc b/test/1914-get-local-instance/local_instance.cc
new file mode 100644
index 0000000..03aa59e
--- /dev/null
+++ b/test/1914-get-local-instance/local_instance.cc
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#include <iostream>
+#include <pthread.h>
+#include <stdio.h>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1914LocalInstance {
+
+extern "C" JNIEXPORT void Java_art_Test1914_00024TargetClass_NativeInstanceMethod(
+    JNIEnv* env, jobject thiz, jobject run) {
+  ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID method = env->GetMethodID(runnable.get(), "run", "()V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallVoidMethod(run, method);
+  if (env->ExceptionCheck()) { return; }
+  ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallStaticVoidMethod(Test1914.get(), report, thiz);
+}
+
+extern "C" JNIEXPORT void Java_art_Test1914_NativeStaticMethod(
+    JNIEnv* env, jclass, jobject run) {
+  ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID method = env->GetMethodID(runnable.get(), "run", "()V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallVoidMethod(run, method);
+  if (env->ExceptionCheck()) { return; }
+  ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallStaticVoidMethod(Test1914.get(), report, nullptr);
+}
+
+}  // namespace Test1914LocalInstance
+}  // namespace art
+
diff --git a/test/1914-get-local-instance/run b/test/1914-get-local-instance/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1914-get-local-instance/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1914-get-local-instance/src/Main.java b/test/1914-get-local-instance/src/Main.java
new file mode 100644
index 0000000..163221e
--- /dev/null
+++ b/test/1914-get-local-instance/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.Test1914.run();
+  }
+}
diff --git a/test/1914-get-local-instance/src/art/Breakpoint.java b/test/1914-get-local-instance/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1914-get-local-instance/src/art/Locals.java b/test/1914-get-local-instance/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1914-get-local-instance/src/art/StackTrace.java b/test/1914-get-local-instance/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1914-get-local-instance/src/art/Suspension.java b/test/1914-get-local-instance/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1914-get-local-instance/src/art/Test1914.java b/test/1914-get-local-instance/src/art/Test1914.java
new file mode 100644
index 0000000..c09f519
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Test1914.java
@@ -0,0 +1,182 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1914 {
+  public static final String TARGET_VAR = "TARGET";
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "' (class: "
+        + (val != null ? val.getClass() : "NULL") + ")");
+  }
+
+  public static void StaticMethod(Runnable safepoint) {
+    safepoint.run();
+    reportValue(null);
+  }
+
+  public static native void NativeStaticMethod(Runnable safepoint);
+
+  public static class TargetClass {
+    public String id;
+    public String toString() { return String.format("TargetClass(\"%s\")", id); }
+    public TargetClass(String id) { this.id = id; }
+
+    public void InstanceMethod(Runnable safepoint) {
+      safepoint.run();
+      reportValue(this);
+    }
+
+    public native void NativeInstanceMethod(Runnable safepoint);
+  }
+
+  public static interface SafepointFunction {
+    public void invoke(
+        Thread thread,
+        Method target,
+        int depth) throws Exception;
+  }
+
+  public static interface GetterFunction {
+    public Object GetVar(Thread t, int depth);
+  }
+
+  public static SafepointFunction NamedGet(final String type, final GetterFunction get) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, int depth) {
+        try {
+          Object res = get.GetVar(t, depth);
+          System.out.println(this + " on " + method + " got value: " + res);
+        } catch (Exception e) {
+          System.out.println(this + " on " + method + " failed due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Get" + type + "\"";
+      }
+    };
+  }
+
+  public static class TestCase {
+    public final Object thiz;
+    public final Method target;
+
+    public TestCase(Method target) {
+      this(null, target);
+    }
+    public TestCase(Object thiz, Method target) {
+      this.thiz = thiz;
+      this.target = target;
+    }
+
+    public static class ThreadPauser implements Runnable {
+      public final Semaphore sem_wakeup_main;
+      public final Semaphore sem_wait;
+
+      public ThreadPauser() {
+        sem_wakeup_main = new Semaphore(0);
+        sem_wait = new Semaphore(0);
+      }
+
+      public void run() {
+        try {
+          sem_wakeup_main.release();
+          sem_wait.acquire();
+        } catch (Exception e) {
+          throw new Error("Error with semaphores!", e);
+        }
+      }
+
+      public void waitForOtherThreadToPause() throws Exception {
+        sem_wakeup_main.acquire();
+      }
+
+      public void wakeupOtherThread() throws Exception {
+        sem_wait.release();
+      }
+    }
+
+    public void exec(final SafepointFunction safepoint) throws Exception {
+      System.out.println("Running " + target + " with " + safepoint + " on remote thread.");
+      final ThreadPauser pause = new ThreadPauser();
+      Thread remote = new Thread(
+          () -> {
+            try {
+              target.invoke(thiz, pause);
+            } catch (Exception e) {
+              throw new Error("Error invoking remote thread " + Thread.currentThread(), e);
+            }
+          },
+          "remote thread for " + target + " with " + safepoint);
+      remote.start();
+      pause.waitForOtherThreadToPause();
+      try {
+        Suspension.suspend(remote);
+        StackTrace.StackFrameData frame = findStackFrame(remote);
+        safepoint.invoke(remote, target, frame.depth);
+      } finally {
+        Suspension.resume(remote);
+        pause.wakeupOtherThread();
+        remote.join();
+      }
+    }
+
+    private StackTrace.StackFrameData findStackFrame(Thread thr) {
+      for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+        if (frame.method.equals(target)) {
+          return frame;
+        }
+      }
+      throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+    }
+  }
+
+  public static Method getMethod(Class<?> klass, String name) throws Exception {
+    return klass.getDeclaredMethod(name, Runnable.class);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final TestCase[] MAIN_TEST_CASES = new TestCase[] {
+      new TestCase(null, getMethod(Test1914.class, "StaticMethod")),
+      new TestCase(null, getMethod(Test1914.class, "NativeStaticMethod")),
+      new TestCase(new TargetClass("InstanceMethodObject"),
+                   getMethod(TargetClass.class, "InstanceMethod")),
+      new TestCase(new TargetClass("NativeInstanceMethodObject"),
+                   getMethod(TargetClass.class, "NativeInstanceMethod")),
+    };
+
+    for (TestCase t: MAIN_TEST_CASES) {
+      t.exec(NamedGet("This", Locals::GetLocalInstance));
+    }
+  }
+}
+
diff --git a/test/1915-get-set-local-current-thread/expected.txt b/test/1915-get-set-local-current-thread/expected.txt
new file mode 100644
index 0000000..de39ca9
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/expected.txt
@@ -0,0 +1,5 @@
+GetLocalInt on current thread!
+From GetLocalInt(), value is 42
+	Value is '42'
+SetLocalInt on current thread!
+	Value is '1337'
diff --git a/test/1915-get-set-local-current-thread/info.txt b/test/1915-get-set-local-current-thread/info.txt
new file mode 100644
index 0000000..c59f50d
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get/set Local variable on current thread.
+
diff --git a/test/1915-get-set-local-current-thread/run b/test/1915-get-set-local-current-thread/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1915-get-set-local-current-thread/src/Main.java b/test/1915-get-set-local-current-thread/src/Main.java
new file mode 100644
index 0000000..47e6767
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/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.Test1915.run();
+  }
+}
diff --git a/test/1915-get-set-local-current-thread/src/art/Breakpoint.java b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1915-get-set-local-current-thread/src/art/Locals.java b/test/1915-get-set-local-current-thread/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1915-get-set-local-current-thread/src/art/StackTrace.java b/test/1915-get-set-local-current-thread/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1915-get-set-local-current-thread/src/art/Suspension.java b/test/1915-get-set-local-current-thread/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1915-get-set-local-current-thread/src/art/Test1915.java b/test/1915-get-set-local-current-thread/src/art/Test1915.java
new file mode 100644
index 0000000..a99a487
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Test1915.java
@@ -0,0 +1,105 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1915 {
+  public static final int SET_VALUE = 1337;
+  public static final String TARGET_VAR = "TARGET";
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "'");
+  }
+  public static interface ThrowRunnable {
+    public void run() throws Exception;
+  }
+
+  public static void IntMethod(ThrowRunnable safepoint) throws Exception {
+    int TARGET = 42;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final Method target = Test1915.class.getDeclaredMethod("IntMethod", ThrowRunnable.class);
+    // Get Variable.
+    System.out.println("GetLocalInt on current thread!");
+    IntMethod(() -> {
+      StackTrace.StackFrameData frame = FindStackFrame(target);
+      int depth = FindExpectedFrameDepth(frame);
+      int slot = FindSlot(frame);
+      int value = Locals.GetLocalVariableInt(Thread.currentThread(), depth, slot);
+      System.out.println("From GetLocalInt(), value is " + value);
+    });
+    // Set Variable.
+    System.out.println("SetLocalInt on current thread!");
+    IntMethod(() -> {
+      StackTrace.StackFrameData frame = FindStackFrame(target);
+      int depth = FindExpectedFrameDepth(frame);
+      int slot = FindSlot(frame);
+      Locals.SetLocalVariableInt(Thread.currentThread(), depth, slot, SET_VALUE);
+    });
+  }
+
+  public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
+    long loc = frame.current_location;
+    for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(TARGET_VAR)) {
+        return var.slot;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
+  }
+
+  public static int FindExpectedFrameDepth(StackTrace.StackFrameData frame) throws Exception {
+    // Adjust the 'frame' depth since it is modified by:
+    // +1 for Get/SetLocalVariableInt in future.
+    // -1 for FindStackFrame
+    // -1 for GetStackTrace
+    // -1 for GetStackTraceNative
+    // ------------------------------
+    // -2
+    return frame.depth - 2;
+  }
+
+  private static StackTrace.StackFrameData FindStackFrame(Method target) {
+    for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) {
+      if (frame.method.equals(target)) {
+        return frame;
+      }
+    }
+    throw new Error("Unable to find stack frame in method " + target);
+  }
+}
+
diff --git a/test/1916-get-set-current-frame/expected.txt b/test/1916-get-set-current-frame/expected.txt
new file mode 100644
index 0000000..343d493
--- /dev/null
+++ b/test/1916-get-set-current-frame/expected.txt
@@ -0,0 +1,4 @@
+From GetLocalInt(), value is 42
+	Value is '42'
+Setting TARGET to 1337
+	Value is '1337'
diff --git a/test/1916-get-set-current-frame/info.txt b/test/1916-get-set-current-frame/info.txt
new file mode 100644
index 0000000..7342af7
--- /dev/null
+++ b/test/1916-get-set-current-frame/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get/set Local variable in the currently executing method frame.
+
diff --git a/test/1916-get-set-current-frame/run b/test/1916-get-set-current-frame/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1916-get-set-current-frame/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1916-get-set-current-frame/src/Main.java b/test/1916-get-set-current-frame/src/Main.java
new file mode 100644
index 0000000..7d0cd21
--- /dev/null
+++ b/test/1916-get-set-current-frame/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.Test1916.run();
+  }
+}
diff --git a/test/1916-get-set-current-frame/src/art/Breakpoint.java b/test/1916-get-set-current-frame/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1916-get-set-current-frame/src/art/Locals.java b/test/1916-get-set-current-frame/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1916-get-set-current-frame/src/art/StackTrace.java b/test/1916-get-set-current-frame/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1916-get-set-current-frame/src/art/Suspension.java b/test/1916-get-set-current-frame/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1916-get-set-current-frame/src/art/Test1916.java b/test/1916-get-set-current-frame/src/art/Test1916.java
new file mode 100644
index 0000000..3e5bce2
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Test1916.java
@@ -0,0 +1,148 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1916 {
+  public static final int SET_VALUE = 1337;
+  public static final String TARGET_VAR = "TARGET";
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "'");
+  }
+
+  public static class IntRunner implements Runnable {
+    private volatile boolean continueBusyLoop;
+    private volatile boolean inBusyLoop;
+    public IntRunner() {
+      this.continueBusyLoop = true;
+      this.inBusyLoop = false;
+    }
+    public void run() {
+      int TARGET = 42;
+      // We will suspend the thread during this loop.
+      while (continueBusyLoop) {
+        inBusyLoop = true;
+      }
+      reportValue(TARGET);
+    }
+    public void waitForBusyLoopStart() { while (!inBusyLoop) {} }
+    public void finish() { continueBusyLoop = false; }
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    runGet();
+    runSet();
+  }
+
+  public static void runGet() throws Exception {
+    Method target = IntRunner.class.getDeclaredMethod("run");
+    // Get Int
+    IntRunner int_runner = new IntRunner();
+    Thread target_get = new Thread(int_runner, "GetLocalInt - Target");
+    target_get.start();
+    int_runner.waitForBusyLoopStart();
+    try {
+      Suspension.suspend(target_get);
+    } catch (Exception e) {
+      System.out.println("FAIL: got " + e);
+      e.printStackTrace();
+      int_runner.finish();
+      target_get.join();
+      return;
+    }
+    try {
+      StackTrace.StackFrameData frame = FindStackFrame(target_get, target);
+      int depth = frame.depth;
+      if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
+      int slot = FindSlot(frame);
+      int value = Locals.GetLocalVariableInt(target_get, depth, slot);
+      System.out.println("From GetLocalInt(), value is " + value);
+    } finally {
+      Suspension.resume(target_get);
+      int_runner.finish();
+      target_get.join();
+    }
+  }
+
+  public static void runSet() throws Exception {
+    Method target = IntRunner.class.getDeclaredMethod("run");
+    // Set Int
+    IntRunner int_runner = new IntRunner();
+    Thread target_set = new Thread(int_runner, "SetLocalInt - Target");
+    target_set.start();
+    int_runner.waitForBusyLoopStart();
+    try {
+      Suspension.suspend(target_set);
+    } catch (Exception e) {
+      System.out.println("FAIL: got " + e);
+      e.printStackTrace();
+      int_runner.finish();
+      target_set.join();
+      return;
+    }
+    try {
+      StackTrace.StackFrameData frame = FindStackFrame(target_set, target);
+      int depth = frame.depth;
+      if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
+      int slot = FindSlot(frame);
+      System.out.println("Setting TARGET to " + SET_VALUE);
+      Locals.SetLocalVariableInt(target_set, depth, slot, SET_VALUE);
+    } finally {
+      Suspension.resume(target_set);
+      int_runner.finish();
+      target_set.join();
+    }
+  }
+
+  public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
+    long loc = frame.current_location;
+    for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(TARGET_VAR)) {
+        return var.slot;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
+  }
+
+  private static StackTrace.StackFrameData FindStackFrame(Thread thr, Method target) {
+    for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+      if (frame.method.equals(target)) {
+        return frame;
+      }
+    }
+    throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+  }
+}
+
diff --git a/test/1917-get-stack-frame/expected.txt b/test/1917-get-stack-frame/expected.txt
new file mode 100644
index 0000000..26217e6
--- /dev/null
+++ b/test/1917-get-stack-frame/expected.txt
@@ -0,0 +1,33 @@
+Recurring 5 times
+'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: 60
+'public void art.Test1917$StackTraceGenerator.run()' line: 81
+'public void art.Test1917$RecurCount.doRecur(int)' line: 103
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.run()' line: 96
+'public static void art.Test1917.run() throws java.lang.Exception' line: 132
+Recurring 5 times on another thread
+'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: 60
+'public void art.Test1917$StackTraceGenerator.run()' line: 81
+'public void art.Test1917$RecurCount.doRecur(int)' line: 103
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.run()' line: 96
+Recurring 5 times on another thread. Stack trace from main thread!
+'public void java.util.concurrent.Semaphore.acquire() throws java.lang.InterruptedException' line: <NOT-DETERMINISTIC>
+'public void art.Test1917$ThreadPauser.run()' line: 46
+'public void art.Test1917$RecurCount.doRecur(int)' line: 103
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.doRecur(int)' line: 101
+'public void art.Test1917$RecurCount.run()' line: 96
diff --git a/test/1917-get-stack-frame/info.txt b/test/1917-get-stack-frame/info.txt
new file mode 100644
index 0000000..e72034a
--- /dev/null
+++ b/test/1917-get-stack-frame/info.txt
@@ -0,0 +1 @@
+Tests stack frame functions of jvmti
diff --git a/test/1917-get-stack-frame/run b/test/1917-get-stack-frame/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1917-get-stack-frame/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1917-get-stack-frame/src/Main.java b/test/1917-get-stack-frame/src/Main.java
new file mode 100644
index 0000000..c055a5c
--- /dev/null
+++ b/test/1917-get-stack-frame/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.Test1917.run();
+  }
+}
diff --git a/test/1917-get-stack-frame/src/art/Breakpoint.java b/test/1917-get-stack-frame/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1917-get-stack-frame/src/art/StackTrace.java b/test/1917-get-stack-frame/src/art/StackTrace.java
new file mode 100644
index 0000000..b12c3df
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/StackTrace.java
@@ -0,0 +1,67 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread());
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1917-get-stack-frame/src/art/Suspension.java b/test/1917-get-stack-frame/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1917-get-stack-frame/src/art/Test1917.java b/test/1917-get-stack-frame/src/art/Test1917.java
new file mode 100644
index 0000000..1057235
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/Test1917.java
@@ -0,0 +1,149 @@
+/*
+ * 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.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.Vector;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1917 {
+  public final static boolean TEST_PRINT_ALL = false;
+
+  public static class ThreadPauser implements Runnable {
+    public Semaphore sem_wakeup_main = new Semaphore(0);
+    public Semaphore sem_wait = new Semaphore(0);
+
+    public void run() {
+      try {
+        sem_wakeup_main.release();
+        sem_wait.acquire();
+      } catch (Exception e) {
+        throw new Error("Error with semaphores!", e);
+      }
+    }
+
+    public void waitForOtherThreadToPause() throws Exception {
+      sem_wakeup_main.acquire();
+    }
+
+    public void wakeupOtherThread() throws Exception {
+      sem_wait.release();
+    }
+  }
+
+  public static class StackTraceGenerator implements Runnable {
+    private final Thread thr;
+    private final Consumer<StackTrace.StackFrameData> con;
+    public StackTraceGenerator(Thread thr, Consumer<StackTrace.StackFrameData> con) {
+      this.thr = thr;
+      this.con = con;
+    }
+
+    public StackTraceGenerator(Consumer<StackTrace.StackFrameData> con) {
+      this(null, con);
+    }
+
+    public Thread getThread() {
+      if (thr == null) {
+        return Thread.currentThread();
+      } else {
+        return thr;
+      }
+    }
+    public void run() {
+      for (StackTrace.StackFrameData s : StackTrace.GetStackTrace(getThread())) {
+        con.accept(s);
+      }
+    }
+  }
+
+  public static class RecurCount implements Runnable {
+    private final int cnt;
+    private final Runnable then;
+    public RecurCount(int cnt, Runnable then) {
+      this.cnt = cnt;
+      this.then = then;
+    }
+
+    public void run() {
+      doRecur(0);
+    }
+
+    public void doRecur(int n) {
+      if (n < cnt) {
+        doRecur(n + 1);
+      } else {
+        then.run();
+      }
+    }
+  }
+
+  public static Consumer<StackTrace.StackFrameData> makePrintStackFramesConsumer()
+      throws Exception {
+    final Method end_method = Test1917.class.getDeclaredMethod("run");
+    return new Consumer<StackTrace.StackFrameData>() {
+      public void accept(StackTrace.StackFrameData data) {
+        if (TEST_PRINT_ALL) {
+          System.out.println(data);
+        } else {
+          Package p = data.method.getDeclaringClass().getPackage();
+          // Filter out anything to do with the testing harness.
+          if (p != null && p.equals(Test1917.class.getPackage())) {
+            System.out.printf("'%s' line: %d\n",
+                data.method,
+                Breakpoint.locationToLine(data.method, data.current_location));
+          } else if (data.method.getDeclaringClass().equals(Semaphore.class)) {
+            System.out.printf("'%s' line: <NOT-DETERMINISTIC>\n", data.method);
+          }
+        }
+      }
+    };
+  }
+
+  public static void run() throws Exception {
+    System.out.println("Recurring 5 times");
+    new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())).run();
+
+    System.out.println("Recurring 5 times on another thread");
+    Thread thr = new Thread(
+        new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())));
+    thr.start();
+    thr.join();
+
+    System.out.println("Recurring 5 times on another thread. Stack trace from main thread!");
+    ThreadPauser pause = new ThreadPauser();
+    Thread thr2 = new Thread(new RecurCount(5, pause));
+    thr2.start();
+    pause.waitForOtherThreadToPause();
+    new StackTraceGenerator(thr2, makePrintStackFramesConsumer()).run();
+    pause.wakeupOtherThread();
+    thr2.join();
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 44cb4f6..fab664a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -252,8 +252,10 @@
         "ti-agent/test_env.cc",
         "ti-agent/breakpoint_helper.cc",
         "ti-agent/common_helper.cc",
+        "ti-agent/locals_helper.cc",
         "ti-agent/redefinition_helper.cc",
         "ti-agent/suspension_helper.cc",
+        "ti-agent/stack_trace_helper.cc",
         "ti-agent/trace_helper.cc",
         // This is the list of non-special OnLoad things and excludes BCI and anything that depends
         // on ART internals.
@@ -292,6 +294,7 @@
         "1905-suspend-native/native_suspend.cc",
         "1908-suspend-native-resume-self/native_suspend_resume.cc",
         "1909-per-agent-tls/agent_tls.cc",
+        "1914-get-local-instance/local_instance.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index ca7db48..90e2600 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -453,6 +453,10 @@
 
 if [ "$USE_JVM" = "y" ]; then
   export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64
+  # Some jvmti tests are flaky without -Xint on the RI.
+  if [ "$IS_JVMTI_TEST" = "y" ]; then
+    FLAGS="${FLAGS} -Xint"
+  fi
   # Xmx is necessary since we don't pass down the ART flags to JVM.
   # We pass the classes2 path whether it's used (src-multidex) or not.
   cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}"
diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc
index 51d3406..0d5cb39 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -15,6 +15,7 @@
  */
 
 #include "jvmti_helper.h"
+#include "test_env.h"
 
 #include <dlfcn.h>
 
@@ -90,6 +91,11 @@
 }
 
 void SetStandardCapabilities(jvmtiEnv* env) {
+  if (IsJVM()) {
+    // RI is more strict about adding capabilities at runtime then ART so just give it everything.
+    SetAllCapabilities(env);
+    return;
+  }
   jvmtiCapabilities caps = GetStandardCapabilities();
   CheckJvmtiError(env, env->AddCapabilities(&caps));
 }
@@ -100,7 +106,7 @@
   CheckJvmtiError(env, env->AddCapabilities(&caps));
 }
 
-bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error) {
+bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error) {
   if (error == JVMTI_ERROR_NONE) {
     return false;
   }
@@ -112,11 +118,11 @@
   }
 
   char* err;
-  CheckJvmtiError(jvmti_env, jvmti_env->GetErrorName(error, &err));
+  CheckJvmtiError(jvmtienv, jvmtienv->GetErrorName(error, &err));
 
   env->ThrowNew(rt_exception.get(), err);
 
-  Deallocate(jvmti_env, err);
+  Deallocate(jvmtienv, err);
   return true;
 }
 
diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h
index 78d238a..a47a402 100644
--- a/test/ti-agent/jvmti_helper.h
+++ b/test/ti-agent/jvmti_helper.h
@@ -43,7 +43,7 @@
 
 // Convert the given error to a RuntimeException with a message derived from the error. Returns
 // true on error, false if error is JVMTI_ERROR_NONE.
-bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error);
+bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error);
 
 class JvmtiDeleter {
  public:
diff --git a/test/ti-agent/locals_helper.cc b/test/ti-agent/locals_helper.cc
new file mode 100644
index 0000000..e284b52
--- /dev/null
+++ b/test/ti-agent/locals_helper.cc
@@ -0,0 +1,210 @@
+/*
+ * 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.
+ */
+
+#include "common_helper.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace common_locals {
+
+static void DeallocateContents(jvmtiLocalVariableEntry* vars, jint nvars) {
+  for (jint i = 0; i < nvars; i++) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].name));
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].signature));
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].generic_signature));
+  }
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_EnableLocalVariableAccess(JNIEnv* env, jclass) {
+  jvmtiCapabilities caps;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetCapabilities(&caps))) {
+    return;
+  }
+  caps.can_access_local_variables = 1;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableObject(JNIEnv* env,
+                                                                 jclass,
+                                                                 jthread t,
+                                                                 jint depth,
+                                                                 jint slot,
+                                                                 jobject val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalObject(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableDouble(JNIEnv* env,
+                                                                 jclass,
+                                                                 jthread t,
+                                                                 jint depth,
+                                                                 jint slot,
+                                                                 jdouble val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalDouble(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableFloat(JNIEnv* env,
+                                                                jclass,
+                                                                jthread t,
+                                                                jint depth,
+                                                                jint slot,
+                                                                jfloat val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalFloat(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableLong(JNIEnv* env,
+                                                               jclass,
+                                                               jthread t,
+                                                               jint depth,
+                                                               jint slot,
+                                                               jlong val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalLong(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableInt(JNIEnv* env,
+                                                              jclass,
+                                                              jthread t,
+                                                              jint depth,
+                                                              jint slot,
+                                                              jint val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalInt(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT jdouble Java_art_Locals_GetLocalVariableDouble(JNIEnv* env,
+                                                                    jclass,
+                                                                    jthread t,
+                                                                    jint depth,
+                                                                    jint slot) {
+  jdouble ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalDouble(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jfloat Java_art_Locals_GetLocalVariableFloat(JNIEnv* env,
+                                                                  jclass,
+                                                                  jthread t,
+                                                                  jint depth,
+                                                                  jint slot) {
+  jfloat ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalFloat(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jlong Java_art_Locals_GetLocalVariableLong(JNIEnv* env,
+                                                                jclass,
+                                                                jthread t,
+                                                                jint depth,
+                                                                jint slot) {
+  jlong ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalLong(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jint Java_art_Locals_GetLocalVariableInt(JNIEnv* env,
+                                                              jclass,
+                                                              jthread t,
+                                                              jint depth,
+                                                              jint slot) {
+  jint ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInt(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalInstance(JNIEnv* env,
+                                                              jclass,
+                                                              jthread t,
+                                                              jint depth) {
+  jobject ret = nullptr;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInstance(t, depth, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalVariableObject(JNIEnv* env,
+                                                                    jclass,
+                                                                    jthread t,
+                                                                    jint depth,
+                                                                    jint slot) {
+  jobject ret = nullptr;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalObject(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobjectArray Java_art_Locals_GetLocalVariableTable(JNIEnv* env,
+                                                                        jclass,
+                                                                        jobject m) {
+  jmethodID method = env->FromReflectedMethod(m);
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  ScopedLocalRef<jclass> klass(env, env->FindClass("art/Locals$VariableDescription"));
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  jint nvars;
+  jvmtiLocalVariableEntry* vars = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->GetLocalVariableTable(method, &nvars, &vars))) {
+    return nullptr;
+  }
+  jobjectArray vars_array = env->NewObjectArray(nvars, klass.get(), nullptr);
+  if (env->ExceptionCheck()) {
+    DeallocateContents(vars, nvars);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+    return nullptr;
+  }
+
+  jmethodID constructor = env->GetMethodID(
+      klass.get(), "<init>", "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  for (jint i = 0; i < nvars; i++) {
+    ScopedLocalRef<jstring> name_string(env, env->NewStringUTF(vars[i].name));
+    ScopedLocalRef<jstring> sig_string(env, env->NewStringUTF(vars[i].signature));
+    ScopedLocalRef<jstring> generic_sig_string(env, env->NewStringUTF(vars[i].generic_signature));
+    jobject var_obj = env->NewObject(klass.get(),
+                                     constructor,
+                                     vars[i].start_location,
+                                     vars[i].length,
+                                     name_string.get(),
+                                     sig_string.get(),
+                                     generic_sig_string.get(),
+                                     vars[i].slot);
+    if (env->ExceptionCheck()) {
+      DeallocateContents(vars, nvars);
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+      return nullptr;
+    }
+    env->SetObjectArrayElement(vars_array, i, var_obj);
+    if (env->ExceptionCheck()) {
+      DeallocateContents(vars, nvars);
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+      return nullptr;
+    }
+  }
+
+  DeallocateContents(vars, nvars);
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+  return vars_array;
+}
+
+}  // namespace common_locals
+}  // namespace art
diff --git a/test/ti-agent/stack_trace_helper.cc b/test/ti-agent/stack_trace_helper.cc
new file mode 100644
index 0000000..f2a8e9a
--- /dev/null
+++ b/test/ti-agent/stack_trace_helper.cc
@@ -0,0 +1,99 @@
+
+/*
+ * 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.
+ */
+
+#include "common_helper.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace common_stack_trace {
+
+extern "C" JNIEXPORT jint JNICALL Java_art_StackTrace_GetStackDepth(
+    JNIEnv* env, jclass, jthread thr) {
+  jint ret;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobjectArray Java_art_StackTrace_nativeGetStackTrace(JNIEnv* env,
+                                                                          jclass,
+                                                                          jthread thr) {
+  jint depth;
+  ScopedLocalRef<jclass> klass(env, env->FindClass("art/StackTrace$StackFrameData"));
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  jmethodID constructor = env->GetMethodID(
+      klass.get(), "<init>", "(Ljava/lang/Thread;Ljava/lang/reflect/Executable;JI)V");
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &depth))) {
+    return nullptr;
+  }
+  // Just give some extra space.
+  depth += 10;
+  jvmtiFrameInfo* frames;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->Allocate(depth * sizeof(jvmtiFrameInfo),
+                                          reinterpret_cast<unsigned char**>(&frames)))) {
+    return nullptr;
+  }
+  jint nframes = 0;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->GetStackTrace(thr, 0, depth, frames, &nframes))) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+    return nullptr;
+  }
+  jobjectArray frames_array = env->NewObjectArray(nframes, klass.get(), nullptr);
+  if (env->ExceptionCheck()) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+    return nullptr;
+  }
+  for (jint i = 0; i < nframes; i++) {
+    jobject jmethod = GetJavaMethod(jvmti_env, env, frames[i].method);
+    if (env->ExceptionCheck()) {
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+      return nullptr;
+    }
+    jobject frame_obj = env->NewObject(klass.get(),
+                                       constructor,
+                                       thr,
+                                       jmethod,
+                                       frames[i].location,
+                                       i);
+    if (env->ExceptionCheck()) {
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+      return nullptr;
+    }
+    env->SetObjectArrayElement(frames_array, i, frame_obj);
+    if (env->ExceptionCheck()) {
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+      return nullptr;
+    }
+  }
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+  return frames_array;
+}
+
+}  // namespace common_stack_trace
+}  // namespace art