Add verifier fallback for JVMTI Get/SetLocalVariable

The JVMTI Get/SetLocalVariable functions used to rely entirely on the
Dex DebugInfo to determine the types of each of the registers. This
could lead to problems since, to prevent possible stack corruption, we
would not allow stack modification if the data was not present.

In order to remove this restriction we will instead make use of the
method verifier to ensure the modification is sensible when the
DebugInfo is not present. Since reconstructing this information using
the verifier is quite slow (compared to reading it from a table) we
will only do this when the table is missing.

Since the verifier lacks some of the information available when
creating the DebugLocalInfo table some semantics will change depending
on if the table is present or not.

 - When the DebugLocalInfo table is not present we cannot always
   distinguish between floats, ints, and other single-register
   primitive types. For simplicity all single-register primitive
   types can be modified and read by both the Float and Int versions
   of the local variable functions.

 - Similarly we cannot always distinguish between long and double
   variables.

 - Reference types are checked against what the verifier thinks they
   need to be according to type unification. This might be more or
   less specific than the types recorded in the functions source code.

 - Constant int/float '0' values and 'null' cannot always be
   differentiated by the verifier. Therefore, one may not always be
   able to modify some null or constant 0 registers.

Test: ./test.py --host
Bug: 131711256

Change-Id: I1c9d857ccdec752bfd4ebad76cc9ad96e143866c
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
index 408ce69..4c6ea21 100644
--- a/openjdkjvmti/ti_method.cc
+++ b/openjdkjvmti/ti_method.cc
@@ -31,22 +31,33 @@
 
 #include "ti_method.h"
 
+#include <initializer_list>
 #include <type_traits>
+#include <variant>
 
+#include "android-base/macros.h"
 #include "arch/context.h"
 #include "art_jvmti.h"
 #include "art_method-inl.h"
 #include "base/enums.h"
+#include "base/globals.h"
+#include "base/macros.h"
 #include "base/mutex-inl.h"
 #include "deopt_manager.h"
 #include "dex/code_item_accessors-inl.h"
+#include "dex/code_item_accessors.h"
 #include "dex/dex_file_annotations.h"
 #include "dex/dex_file_types.h"
+#include "dex/dex_instruction.h"
+#include "dex/dex_instruction_iterator.h"
 #include "dex/modifiers.h"
+#include "dex/primitive.h"
 #include "events-inl.h"
 #include "gc_root-inl.h"
+#include "handle.h"
 #include "jit/jit.h"
 #include "jni/jni_internal.h"
+#include "jvmti.h"
 #include "mirror/class-inl.h"
 #include "mirror/class_loader.h"
 #include "mirror/object-inl.h"
@@ -56,13 +67,18 @@
 #include "obj_ptr.h"
 #include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
+#include "scoped_thread_state_change.h"
 #include "stack.h"
 #include "thread-current-inl.h"
 #include "thread.h"
 #include "thread_list.h"
+#include "ti_logging.h"
 #include "ti_stack.h"
 #include "ti_thread.h"
 #include "ti_phase.h"
+#include "verifier/register_line-inl.h"
+#include "verifier/reg_type-inl.h"
+#include "verifier/method_verifier-inl.h"
 
 namespace openjdkjvmti {
 
@@ -526,10 +542,21 @@
 
 class CommonLocalVariableClosure : public art::Closure {
  public:
-  CommonLocalVariableClosure(jint depth, jint slot)
-      : result_(ERR(INTERNAL)), depth_(depth), slot_(slot) {}
+  // The verifier isn't always able to be as specific as the local-variable-table. We can only get
+  // 32-bit, 64-bit or reference.
+  enum class VerifierPrimitiveType {
+    k32BitValue,  // float, int, short, char, boolean, byte
+    k64BitValue,  // double, long
+    kReferenceValue,  // Object
+    kZeroValue,  // null or zero constant. Might be either k32BitValue or kReferenceValue
+  };
 
-  void Run(art::Thread* self) override REQUIRES(art::Locks::mutator_lock_) {
+  using SlotType = std::variant<art::Primitive::Type, VerifierPrimitiveType>;
+
+  CommonLocalVariableClosure(jvmtiEnv* jvmti, jint depth, jint slot)
+      : jvmti_(jvmti), result_(ERR(INTERNAL)), depth_(depth), slot_(slot) {}
+
+  void Run(art::Thread* self) override REQUIRES_SHARED(art::Locks::mutator_lock_) {
     art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
     bool needs_instrument;
     {
@@ -560,7 +587,7 @@
         return;
       }
       std::string descriptor;
-      art::Primitive::Type slot_type = art::Primitive::kPrimVoid;
+      SlotType slot_type{ art::Primitive::kPrimVoid };
       jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type);
       if (err != OK) {
         result_ = err;
@@ -587,56 +614,190 @@
   virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
       REQUIRES_SHARED(art::Locks::mutator_lock_) = 0;
   virtual jvmtiError GetTypeError(art::ArtMethod* method,
-                                  art::Primitive::Type type,
+                                  SlotType type,
                                   const std::string& descriptor)
       REQUIRES_SHARED(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();
-    if (dex_file == nullptr) {
+                         /*out*/SlotType* type)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  jvmtiError InferSlotTypeFromVerifier(art::ArtMethod* method,
+                                       uint32_t dex_pc,
+                                       /*out*/ std::string* descriptor,
+                                       /*out*/ SlotType* type)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::Thread* self = art::Thread::Current();
+    art::StackHandleScope<2> hs(self);
+    std::unique_ptr<art::verifier::MethodVerifier> verifier(
+        art::verifier::MethodVerifier::CalculateVerificationInfo(
+            self,
+            method,
+            hs.NewHandle(method->GetDexCache()),
+            hs.NewHandle(method->GetDeclaringClass()->GetClassLoader())));
+    if (verifier == nullptr) {
+      JVMTI_LOG(WARNING, jvmti_) << "Unable to extract verification information from "
+                                 << method->PrettyMethod() << " due to hard verification failures! "
+                                 << "How did this method even get loaded!";
+      return ERR(INTERNAL);
+    }
+    art::verifier::RegisterLine* line = verifier->GetRegLine(dex_pc);
+    if (line == nullptr) {
+      JVMTI_LOG(WARNING, jvmti_) << "Unable to determine register line at dex-pc " << dex_pc
+                                 << " for method " << method->PrettyMethod();
       return ERR(OPAQUE_FRAME);
     }
-    art::CodeItemDebugInfoAccessor accessor(method->DexInstructionDebugInfo());
-    if (!accessor.HasCodeItem()) {
-      return ERR(OPAQUE_FRAME);
-    }
-    bool found = false;
-    *type = art::Primitive::kPrimVoid;
-    descriptor->clear();
-    auto visitor = [&](const art::DexFile::LocalInfo& entry) {
-      if (!found &&
-          entry.start_address_ <= dex_pc &&
-          entry.end_address_ > dex_pc &&
-          entry.reg_ == slot_) {
-        found = true;
-        *type = art::Primitive::GetType(entry.descriptor_[0]);
-        *descriptor = entry.descriptor_;
-      }
-    };
-    if (!accessor.DecodeDebugLocalInfo(method->IsStatic(), method->GetDexMethodIndex(), visitor) ||
-        !found) {
-      // Something went wrong with decoding the debug information. It might as well not be there.
+    const art::verifier::RegType& rt = line->GetRegisterType(verifier.get(), slot_);
+    if (rt.IsUndefined()) {
+      return ERR(INVALID_SLOT);
+    } else if (rt.IsNonZeroReferenceTypes() || rt.IsNull()) {
+      *descriptor = (rt.HasClass() ? rt.GetDescriptor() : "Ljava/lang/Object;");
+      *type = VerifierPrimitiveType::kReferenceValue;
+      return OK;
+    } else if (rt.IsZero()) {
+      *descriptor = "I";
+      *type = VerifierPrimitiveType::kZeroValue;
+      return OK;
+    } else if (rt.IsCategory1Types()) {
+      *descriptor = "I";
+      *type = VerifierPrimitiveType::k32BitValue;
+      return OK;
+    } else if (rt.IsCategory2Types() && rt.IsLowHalf()) {
+      *descriptor = "J";
+      *type = VerifierPrimitiveType::k64BitValue;
+      return OK;
+    } else {
+      // The slot doesn't have a type. Must not be valid here.
       return ERR(INVALID_SLOT);
     }
-    return OK;
   }
 
+  constexpr VerifierPrimitiveType SquashType(SlotType t) {
+    if (std::holds_alternative<art::Primitive::Type>(t)) {
+      switch (std::get<art::Primitive::Type>(t)) {
+        // 32-bit primitives
+        case art::Primitive::kPrimByte:
+        case art::Primitive::kPrimChar:
+        case art::Primitive::kPrimInt:
+        case art::Primitive::kPrimShort:
+        case art::Primitive::kPrimBoolean:
+        case art::Primitive::kPrimFloat:
+          return VerifierPrimitiveType::k32BitValue;
+        // 64-bit primitives
+        case art::Primitive::kPrimLong:
+        case art::Primitive::kPrimDouble:
+          return VerifierPrimitiveType::k64BitValue;
+        case art::Primitive::kPrimNot:
+          return VerifierPrimitiveType::kReferenceValue;
+        case art::Primitive::kPrimVoid:
+          LOG(FATAL) << "Got kPrimVoid";
+          UNREACHABLE();
+      }
+    } else {
+      return std::get<VerifierPrimitiveType>(t);
+    }
+  }
+
+  jvmtiEnv* jvmti_;
   jvmtiError result_;
   jint depth_;
   jint slot_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CommonLocalVariableClosure);
 };
 
+std::ostream& operator<<(std::ostream& os,
+                         CommonLocalVariableClosure::VerifierPrimitiveType state) {
+  switch (state) {
+    case CommonLocalVariableClosure::VerifierPrimitiveType::k32BitValue:
+      return os << "32BitValue";
+    case CommonLocalVariableClosure::VerifierPrimitiveType::k64BitValue:
+      return os << "64BitValue";
+    case CommonLocalVariableClosure::VerifierPrimitiveType::kReferenceValue:
+      return os << "ReferenceValue";
+    case CommonLocalVariableClosure::VerifierPrimitiveType::kZeroValue:
+      return os << "ZeroValue";
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, CommonLocalVariableClosure::SlotType state) {
+  if (std::holds_alternative<art::Primitive::Type>(state)) {
+    return os << "Primitive::Type[" << std::get<art::Primitive::Type>(state) << "]";
+  } else {
+    return os << "VerifierPrimitiveType["
+              << std::get<CommonLocalVariableClosure::VerifierPrimitiveType>(state) << "]";
+  }
+}
+
+jvmtiError CommonLocalVariableClosure::GetSlotType(art::ArtMethod* method,
+                                                   uint32_t dex_pc,
+                                                   /*out*/ std::string* descriptor,
+                                                   /*out*/ SlotType* type) {
+  const art::DexFile* dex_file = method->GetDexFile();
+  if (dex_file == nullptr) {
+    return ERR(OPAQUE_FRAME);
+  }
+  art::CodeItemDebugInfoAccessor accessor(method->DexInstructionDebugInfo());
+  if (!accessor.HasCodeItem()) {
+    return ERR(OPAQUE_FRAME);
+  }
+  bool found = false;
+  *type = art::Primitive::kPrimVoid;
+  descriptor->clear();
+  auto visitor = [&](const art::DexFile::LocalInfo& entry) {
+    if (!found && entry.start_address_ <= dex_pc && entry.end_address_ > dex_pc &&
+        entry.reg_ == slot_) {
+      found = true;
+      *type = art::Primitive::GetType(entry.descriptor_[0]);
+      *descriptor = entry.descriptor_;
+    }
+  };
+  if (!accessor.DecodeDebugLocalInfo(method->IsStatic(), method->GetDexMethodIndex(), visitor) ||
+      !found) {
+    // Something went wrong with decoding the debug information. It might as well not be there.
+    // Try to find the type with the verifier.
+    // TODO This is very slow.
+    return InferSlotTypeFromVerifier(method, dex_pc, descriptor, type);
+  } else if (art::kIsDebugBuild) {
+    std::string type_unused;
+    SlotType verifier_type{ art::Primitive::kPrimVoid };
+    DCHECK_EQ(InferSlotTypeFromVerifier(method, dex_pc, &type_unused, &verifier_type), OK)
+        << method->PrettyMethod() << " failed to verify!";
+    if (*type == SlotType{ art::Primitive::kPrimNot }) {
+      // We cannot distinguish between a constant 0 and a null reference so we return that it is a
+      // 32bit value (Due to the way references are read by the interpreter this is safe even if
+      // it's modified, the value will remain null). This is not ideal since it prevents modifying
+      // locals in some circumstances but generally is not a big deal (since one can just modify it
+      // later once it's been determined to be a reference by a later instruction).
+      DCHECK(verifier_type == SlotType { VerifierPrimitiveType::kZeroValue } ||
+             verifier_type == SlotType { VerifierPrimitiveType::kReferenceValue })
+          << "Verifier disagrees on type of slot! debug: " << *type
+          << " verifier: " << verifier_type;
+    } else if (verifier_type == SlotType { VerifierPrimitiveType::kZeroValue }) {
+      DCHECK(VerifierPrimitiveType::k32BitValue == SquashType(*type) ||
+             VerifierPrimitiveType::kReferenceValue == SquashType(*type))
+          << "Verifier disagrees on type of slot! debug: " << *type
+          << " verifier: " << verifier_type;
+    } else {
+      DCHECK_EQ(SquashType(verifier_type), SquashType(*type))
+          << "Verifier disagrees on type of slot! debug: " << *type
+          << " verifier: " << verifier_type;
+    }
+  }
+  return OK;
+}
+
 class GetLocalVariableClosure : public CommonLocalVariableClosure {
  public:
-  GetLocalVariableClosure(jint depth,
+  GetLocalVariableClosure(jvmtiEnv* jvmti,
+                          jint depth,
                           jint slot,
                           art::Primitive::Type type,
                           jvalue* val)
-      : CommonLocalVariableClosure(depth, slot),
+      : CommonLocalVariableClosure(jvmti, depth, slot),
         type_(type),
         val_(val),
         obj_val_(nullptr) {}
@@ -656,22 +817,61 @@
   }
 
  protected:
-  jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED,
-                          art::Primitive::Type slot_type,
-                          const std::string& descriptor ATTRIBUTE_UNUSED)
-      override REQUIRES_SHARED(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:
+  jvmtiError
+  GetTypeError(art::ArtMethod* method, SlotType slot_type, const std::string& descriptor) override
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    jvmtiError res = GetTypeErrorInner(method, slot_type, descriptor);
+    if (res == ERR(TYPE_MISMATCH)) {
+      JVMTI_LOG(INFO, jvmti_) << "Unable to Get local variable in slot " << slot_ << ". Expected"
+                              << " slot to be of type compatible with " << SlotType { type_ }
+                              << " but slot is " << slot_type;
+    } else if (res != OK) {
+      JVMTI_LOG(INFO, jvmti_) << "Unable to get local variable in slot " << slot_ << ".";
+    }
+    return res;
+  }
+
+  jvmtiError GetTypeErrorInner(art::ArtMethod* method ATTRIBUTE_UNUSED,
+                               SlotType slot_type,
+                               const std::string& descriptor ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    switch (type_) {
       case art::Primitive::kPrimFloat:
-      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimInt: {
+        if (std::holds_alternative<VerifierPrimitiveType>(slot_type)) {
+          return (slot_type == SlotType { VerifierPrimitiveType::k32BitValue } ||
+                  slot_type == SlotType { VerifierPrimitiveType::kZeroValue })
+                     ? OK
+                     : ERR(TYPE_MISMATCH);
+        } else if (type_ == art::Primitive::kPrimFloat ||
+                   slot_type == SlotType { art::Primitive::kPrimFloat }) {
+          // Check that we are actually a float.
+          return (SlotType { type_ } == slot_type) ? OK : ERR(TYPE_MISMATCH);
+        } else {
+          // Some smaller int type.
+          return SquashType(slot_type) == SquashType(SlotType { type_ }) ? OK : ERR(TYPE_MISMATCH);
+        }
+      }
+      case art::Primitive::kPrimLong:
+      case art::Primitive::kPrimDouble: {
+        // todo
+        if (std::holds_alternative<VerifierPrimitiveType>(slot_type)) {
+          return (slot_type == SlotType { VerifierPrimitiveType::k64BitValue })
+                     ? OK
+                     : ERR(TYPE_MISMATCH);
+        } else {
+          return slot_type == SlotType { type_ } ? OK : ERR(TYPE_MISMATCH);
+        }
+      }
       case art::Primitive::kPrimNot:
-        return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+        return (SquashType(slot_type) == VerifierPrimitiveType::kReferenceValue ||
+                SquashType(slot_type) == VerifierPrimitiveType::kZeroValue)
+                   ? OK
+                   : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimShort:
+      case art::Primitive::kPrimChar:
+      case art::Primitive::kPrimByte:
+      case art::Primitive::kPrimBoolean:
       case art::Primitive::kPrimVoid:
         LOG(FATAL) << "Unexpected primitive type " << slot_type;
         UNREACHABLE();
@@ -735,7 +935,7 @@
   jobject obj_val_;
 };
 
-jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env,
                                                jthread thread,
                                                jint depth,
                                                jint slot,
@@ -753,7 +953,7 @@
     art::Locks::thread_list_lock_->ExclusiveUnlock(self);
     return err;
   }
-  GetLocalVariableClosure c(depth, slot, type, val);
+  GetLocalVariableClosure c(env, depth, slot, type, val);
   // RequestSynchronousCheckpoint releases the thread_list_lock_ as a part of its execution.
   if (!target->RequestSynchronousCheckpoint(&c)) {
     return ERR(THREAD_NOT_ALIVE);
@@ -764,49 +964,100 @@
 
 class SetLocalVariableClosure : public CommonLocalVariableClosure {
  public:
-  SetLocalVariableClosure(art::Thread* caller,
+  SetLocalVariableClosure(jvmtiEnv* jvmti,
+                          art::Thread* caller,
                           jint depth,
                           jint slot,
                           art::Primitive::Type type,
                           jvalue val)
-      : CommonLocalVariableClosure(depth, slot), caller_(caller), type_(type), val_(val) {}
+      : CommonLocalVariableClosure(jvmti, depth, slot), caller_(caller), type_(type), val_(val) {}
 
  protected:
-  jvmtiError GetTypeError(art::ArtMethod* method,
-                          art::Primitive::Type slot_type,
-                          const std::string& descriptor)
-      override REQUIRES_SHARED(art::Locks::mutator_lock_) {
-    switch (slot_type) {
-      case art::Primitive::kPrimNot: {
-        if (type_ != art::Primitive::kPrimNot) {
+  jvmtiError
+  GetTypeError(art::ArtMethod* method, SlotType slot_type, const std::string& descriptor) override
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    jvmtiError res = GetTypeErrorInner(method, slot_type, descriptor);
+    if (res != OK) {
+      if (res == ERR(TYPE_MISMATCH)) {
+        std::ostringstream desc_exp;
+        std::ostringstream desc_set;
+        if (type_ == art::Primitive::kPrimNot) {
+          desc_exp << " (type: " << descriptor << ")";
+          art::ObjPtr<art::mirror::Object> new_val(art::Thread::Current()->DecodeJObject(val_.l));
+          desc_set << " (type: "
+                  << (new_val.IsNull() ? "NULL" : new_val->GetClass()->PrettyDescriptor()) << ")";
+        }
+        JVMTI_LOG(INFO, jvmti_) << "Unable to Set local variable in slot " << slot_ << ". Expected"
+                                << " slot to be of type compatible with " << SlotType{ type_ }
+                                << desc_set.str() << " but slot is " << slot_type << desc_exp.str();
+      } else {
+        JVMTI_LOG(INFO, jvmti_) << "Unable to set local variable in slot " << slot_ << ". "
+                                << err_.str();
+      }
+    }
+    return res;
+  }
+
+  jvmtiError
+  GetTypeErrorInner(art::ArtMethod* method, SlotType slot_type, const std::string& descriptor)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    switch (SquashType(SlotType{ type_ })) {
+      case VerifierPrimitiveType::k32BitValue: {
+        if (slot_type == SlotType{ VerifierPrimitiveType::kZeroValue }) {
+          if (val_.i == 0) {
+            return OK;
+          } else {
+            err_ << "Cannot determine if slot " << slot_ << " is a null reference or 32bit "
+                 << "constant. Cannot allow writing to slot.";
+            return ERR(INTERNAL);
+          }
+        } else if (SquashType(slot_type) != VerifierPrimitiveType::k32BitValue) {
+          return ERR(TYPE_MISMATCH);
+        } else if (slot_type == SlotType { VerifierPrimitiveType::k32BitValue } ||
+                   slot_type == SlotType { type_ }) {
+          return OK;
+        } else if (type_ == art::Primitive::kPrimFloat ||
+                   slot_type == SlotType { art::Primitive::kPrimFloat }) {
+          // we should have hit the get == type_ above
+          return ERR(TYPE_MISMATCH);
+        } else {
+          // Some smaller type then int.
+          return OK;
+        }
+      }
+      case VerifierPrimitiveType::k64BitValue: {
+        if (slot_type == SlotType { VerifierPrimitiveType::k64BitValue } ||
+            slot_type == SlotType { type_ }) {
+          return OK;
+        } else {
+          return ERR(TYPE_MISMATCH);
+        }
+      }
+      case VerifierPrimitiveType::kReferenceValue: {
+        if (SquashType(slot_type) != VerifierPrimitiveType::kReferenceValue &&
+            SquashType(slot_type) != VerifierPrimitiveType::kZeroValue) {
           return ERR(TYPE_MISMATCH);
         } else if (val_.l == nullptr) {
           return OK;
+        } else if (slot_type == SlotType { VerifierPrimitiveType::kZeroValue }) {
+          err_ << "Cannot determine if slot " << slot_ << " is a null "
+               << "reference or 32bit constant. Cannot allow writing to slot.";
+          return ERR(INTERNAL);
         } else {
           art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
-          art::ObjPtr<art::mirror::Class> set_class =
-              caller_->DecodeJObject(val_.l)->GetClass();
+          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());
+          DCHECK(!slot_class.IsNull()) << descriptor << " slot: " << slot_type;
           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;
+      case VerifierPrimitiveType::kZeroValue: {
+        LOG(FATAL) << "Illegal result from SquashType of art::Primitive::Type " << type_;
         UNREACHABLE();
+      }
     }
   }
 
@@ -857,9 +1108,10 @@
   art::Thread* caller_;
   art::Primitive::Type type_;
   jvalue val_;
+  std::ostringstream err_;
 };
 
-jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env,
                                                jthread thread,
                                                jint depth,
                                                jint slot,
@@ -880,7 +1132,7 @@
     art::Locks::thread_list_lock_->ExclusiveUnlock(self);
     return err;
   }
-  SetLocalVariableClosure c(self, depth, slot, type, val);
+  SetLocalVariableClosure c(env, self, depth, slot, type, val);
   // RequestSynchronousCheckpoint releases the thread_list_lock_ as a part of its execution.
   if (!target->RequestSynchronousCheckpoint(&c)) {
     return ERR(THREAD_NOT_ALIVE);