Fixes for constant method handles

Add support for constant direct and interface method handles in the
DEX file.

Add error handling for field and method resolution failures in
ClassLinker::ResolveMethodHandle().

Bug: 36957105
Test: art/test/run-test --host 952-invoke-custom-kinds
Change-Id: I91a2a23ba3365310eccb8cadd193b62f57e5811c
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index a8fdeca..e75f75b 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -473,6 +473,11 @@
                                  start + layout.MethodTypesOffset(),
                                  dex_cache);
     }
+    if (dex_cache->GetResolvedCallSites() != nullptr) {
+      AddDexCacheArrayRelocation(dex_cache->GetResolvedCallSites(),
+                                 start + layout.CallSitesOffset(),
+                                 dex_cache);
+    }
   }
 }
 
diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc
index df0169f..65232bf 100644
--- a/dexdump/dexdump.cc
+++ b/dexdump/dexdump.cc
@@ -1582,31 +1582,53 @@
 
 static void dumpMethodHandle(const DexFile* pDexFile, u4 idx) {
   const DexFile::MethodHandleItem& mh = pDexFile->GetMethodHandle(idx);
+  const char* type = nullptr;
+  bool is_instance = false;
   bool is_invoke = false;
-  const char* type;
   switch (static_cast<DexFile::MethodHandleType>(mh.method_handle_type_)) {
     case DexFile::MethodHandleType::kStaticPut:
       type = "put-static";
+      is_instance = false;
+      is_invoke = false;
       break;
     case DexFile::MethodHandleType::kStaticGet:
       type = "get-static";
+      is_instance = false;
+      is_invoke = false;
       break;
     case DexFile::MethodHandleType::kInstancePut:
       type = "put-instance";
+      is_instance = true;
+      is_invoke = false;
       break;
     case DexFile::MethodHandleType::kInstanceGet:
       type = "get-instance";
+      is_instance = true;
+      is_invoke = false;
       break;
     case DexFile::MethodHandleType::kInvokeStatic:
       type = "invoke-static";
+      is_instance = false;
       is_invoke = true;
       break;
     case DexFile::MethodHandleType::kInvokeInstance:
       type = "invoke-instance";
+      is_instance = true;
       is_invoke = true;
       break;
     case DexFile::MethodHandleType::kInvokeConstructor:
       type = "invoke-constructor";
+      is_instance = true;
+      is_invoke = true;
+      break;
+    case DexFile::MethodHandleType::kInvokeDirect:
+      type = "invoke-direct";
+      is_instance = true;
+      is_invoke = true;
+      break;
+    case DexFile::MethodHandleType::kInvokeInterface:
+      type = "invoke-interface";
+      is_instance = true;
       is_invoke = true;
       break;
   }
@@ -1614,16 +1636,26 @@
   const char* declaring_class;
   const char* member;
   std::string member_type;
-  if (is_invoke) {
-    const DexFile::MethodId& method_id = pDexFile->GetMethodId(mh.field_or_method_idx_);
-    declaring_class = pDexFile->GetMethodDeclaringClassDescriptor(method_id);
-    member = pDexFile->GetMethodName(method_id);
-    member_type = pDexFile->GetMethodSignature(method_id).ToString();
+  if (type != nullptr) {
+    if (is_invoke) {
+      const DexFile::MethodId& method_id = pDexFile->GetMethodId(mh.field_or_method_idx_);
+      declaring_class = pDexFile->GetMethodDeclaringClassDescriptor(method_id);
+      member = pDexFile->GetMethodName(method_id);
+      member_type = pDexFile->GetMethodSignature(method_id).ToString();
+    } else {
+      const DexFile::FieldId& field_id = pDexFile->GetFieldId(mh.field_or_method_idx_);
+      declaring_class = pDexFile->GetFieldDeclaringClassDescriptor(field_id);
+      member = pDexFile->GetFieldName(field_id);
+      member_type = pDexFile->GetFieldTypeDescriptor(field_id);
+    }
+    if (is_instance) {
+      member_type = android::base::StringPrintf("(%s%s", declaring_class, member_type.c_str() + 1);
+    }
   } else {
-    const DexFile::FieldId& field_id = pDexFile->GetFieldId(mh.field_or_method_idx_);
-    declaring_class = pDexFile->GetFieldDeclaringClassDescriptor(field_id);
-    member = pDexFile->GetFieldName(field_id);
-    member_type = pDexFile->GetFieldTypeDescriptor(field_id);
+    type = "?";
+    declaring_class = "?";
+    member = "?";
+    member_type = "?";
   }
 
   if (gOptions.outputFormat == OUTPUT_PLAIN) {
diff --git a/dexlayout/dex_ir.cc b/dexlayout/dex_ir.cc
index a200d8d..5913832 100644
--- a/dexlayout/dex_ir.cc
+++ b/dexlayout/dex_ir.cc
@@ -793,8 +793,10 @@
       static_cast<DexFile::MethodHandleType>(disk_method_handle.method_handle_type_);
   bool is_invoke = type == DexFile::MethodHandleType::kInvokeStatic ||
                    type == DexFile::MethodHandleType::kInvokeInstance ||
-                   type == DexFile::MethodHandleType::kInvokeConstructor;
-  static_assert(DexFile::MethodHandleType::kLast == DexFile::MethodHandleType::kInvokeConstructor,
+                   type == DexFile::MethodHandleType::kInvokeConstructor ||
+                   type == DexFile::MethodHandleType::kInvokeDirect ||
+                   type == DexFile::MethodHandleType::kInvokeInterface;
+  static_assert(DexFile::MethodHandleType::kLast == DexFile::MethodHandleType::kInvokeInterface,
                 "Unexpected method handle types.");
   IndexedItem* field_or_method_id;
   if (is_invoke) {
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 10e0bd2..7aab9de 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -8256,65 +8256,139 @@
   const DexFile* const dex_file = referrer->GetDexFile();
   const DexFile::MethodHandleItem& mh = dex_file->GetMethodHandle(method_handle_idx);
 
-  union {
-    ArtField* field;
-    ArtMethod* method;
-    uintptr_t field_or_method;
-  } target;
-  uint32_t num_params;
-  mirror::MethodHandle::Kind kind;
+  ArtField* target_field = nullptr;
+  ArtMethod* target_method = nullptr;
+
   DexFile::MethodHandleType handle_type =
       static_cast<DexFile::MethodHandleType>(mh.method_handle_type_);
   switch (handle_type) {
     case DexFile::MethodHandleType::kStaticPut: {
+      target_field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */);
+      break;
+    }
+    case DexFile::MethodHandleType::kStaticGet: {
+      target_field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */);
+      break;
+    }
+    case DexFile::MethodHandleType::kInstancePut: {
+      target_field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */);
+      break;
+    }
+    case DexFile::MethodHandleType::kInstanceGet: {
+      target_field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */);
+      break;
+    }
+    case DexFile::MethodHandleType::kInvokeStatic: {
+      target_method = ResolveMethod<kNoICCECheckForCache>(self,
+                                                          mh.field_or_method_idx_,
+                                                          referrer,
+                                                          InvokeType::kStatic);
+      break;
+    }
+    case DexFile::MethodHandleType::kInvokeInstance: {
+      target_method = ResolveMethod<kNoICCECheckForCache>(self,
+                                                          mh.field_or_method_idx_,
+                                                          referrer,
+                                                          InvokeType::kVirtual);
+      break;
+    }
+    case DexFile::MethodHandleType::kInvokeConstructor: {
+      UNIMPLEMENTED(FATAL) << "Invoke constructor is implemented as a transform.";
+      break;
+    }
+    case DexFile::MethodHandleType::kInvokeDirect: {
+      target_method = ResolveMethod<kNoICCECheckForCache>(self,
+                                                          mh.field_or_method_idx_,
+                                                          referrer,
+                                                          InvokeType::kDirect);
+      break;
+    }
+    case DexFile::MethodHandleType::kInvokeInterface: {
+      target_method = ResolveMethod<kNoICCECheckForCache>(self,
+                                                          mh.field_or_method_idx_,
+                                                          referrer,
+                                                          InvokeType::kInterface);
+      break;
+    }
+  }
+
+  if (target_field != nullptr) {
+    ObjPtr<mirror::Class> target_class = target_field->GetDeclaringClass();
+    ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
+    if (!referring_class->CanAccessMember(target_class, target_field->GetAccessFlags())) {
+      ThrowIllegalAccessErrorField(referring_class, target_field);
+      return nullptr;
+    }
+  } else if (target_method != nullptr) {
+    ObjPtr<mirror::Class> target_class = target_method->GetDeclaringClass();
+    ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
+    if (!referring_class->CanAccessMember(target_class, target_method->GetAccessFlags())) {
+      ThrowIllegalAccessErrorMethod(referring_class, target_method);
+      return nullptr;
+    }
+  } else {
+    // Common check for resolution failure.
+    DCHECK(Thread::Current()->IsExceptionPending());
+    return nullptr;
+  }
+
+  // Determine the kind and number of parameters after it's safe to
+  // follow the field or method pointer.
+  mirror::MethodHandle::Kind kind;
+  uint32_t num_params;
+  switch (handle_type) {
+    case DexFile::MethodHandleType::kStaticPut: {
       kind = mirror::MethodHandle::Kind::kStaticPut;
-      target.field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */);
       num_params = 1;
       break;
     }
     case DexFile::MethodHandleType::kStaticGet: {
       kind = mirror::MethodHandle::Kind::kStaticGet;
-      target.field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */);
       num_params = 0;
       break;
     }
     case DexFile::MethodHandleType::kInstancePut: {
       kind = mirror::MethodHandle::Kind::kInstancePut;
-      target.field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */);
       num_params = 2;
       break;
     }
     case DexFile::MethodHandleType::kInstanceGet: {
       kind = mirror::MethodHandle::Kind::kInstanceGet;
-      target.field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */);
       num_params = 1;
       break;
     }
     case DexFile::MethodHandleType::kInvokeStatic: {
       kind = mirror::MethodHandle::Kind::kInvokeStatic;
-      target.method = ResolveMethod<kNoICCECheckForCache>(self,
-                                                          mh.field_or_method_idx_,
-                                                          referrer,
-                                                          InvokeType::kStatic);
       uint32_t shorty_length;
-      target.method->GetShorty(&shorty_length);
-      num_params = shorty_length - 1;  // Remove 1 for return value.
+      target_method->GetShorty(&shorty_length);
+      num_params = shorty_length - 1;  // Remove 1 for the return value.
       break;
     }
     case DexFile::MethodHandleType::kInvokeInstance: {
       kind = mirror::MethodHandle::Kind::kInvokeVirtual;
-      target.method = ResolveMethod<kNoICCECheckForCache>(self,
-                                                          mh.field_or_method_idx_,
-                                                          referrer,
-                                                          InvokeType::kVirtual);
       uint32_t shorty_length;
-      target.method->GetShorty(&shorty_length);
-      num_params = shorty_length - 1;  // Remove 1 for return value.
+      target_method->GetShorty(&shorty_length);
+      num_params = shorty_length;  // Add 1 for the receiver, remove 1 for the return value.
       break;
     }
     case DexFile::MethodHandleType::kInvokeConstructor: {
       UNIMPLEMENTED(FATAL) << "Invoke constructor is implemented as a transform.";
       num_params = 0;
+      break;
+    }
+    case DexFile::MethodHandleType::kInvokeDirect: {
+      kind = mirror::MethodHandle::Kind::kInvokeDirect;
+      uint32_t shorty_length;
+      target_method->GetShorty(&shorty_length);
+      num_params = shorty_length;  // Add 1 for the receiver, remove 1 for the return value.
+      break;
+    }
+    case DexFile::MethodHandleType::kInvokeInterface: {
+      kind = mirror::MethodHandle::Kind::kInvokeInterface;
+      uint32_t shorty_length;
+      target_method->GetShorty(&shorty_length);
+      num_params = shorty_length;  // Add 1 for the receiver, remove 1 for the return value.
+      break;
     }
   }
 
@@ -8331,44 +8405,51 @@
   Handle<mirror::Class> return_type;
   switch (handle_type) {
     case DexFile::MethodHandleType::kStaticPut: {
-      method_params->Set(0, target.field->GetType<true>());
+      method_params->Set(0, target_field->GetType<true>());
       return_type = hs.NewHandle(FindPrimitiveClass('V'));
       break;
     }
     case DexFile::MethodHandleType::kStaticGet: {
-      return_type = hs.NewHandle(target.field->GetType<true>());
+      return_type = hs.NewHandle(target_field->GetType<true>());
       break;
     }
     case DexFile::MethodHandleType::kInstancePut: {
-      method_params->Set(0, target.field->GetDeclaringClass());
-      method_params->Set(1, target.field->GetType<true>());
+      method_params->Set(0, target_field->GetDeclaringClass());
+      method_params->Set(1, target_field->GetType<true>());
       return_type = hs.NewHandle(FindPrimitiveClass('V'));
       break;
     }
     case DexFile::MethodHandleType::kInstanceGet: {
-      method_params->Set(0, target.field->GetDeclaringClass());
-      return_type = hs.NewHandle(target.field->GetType<true>());
+      method_params->Set(0, target_field->GetDeclaringClass());
+      return_type = hs.NewHandle(target_field->GetType<true>());
       break;
     }
-    case DexFile::MethodHandleType::kInvokeStatic:
-    case DexFile::MethodHandleType::kInvokeInstance: {
+    case DexFile::MethodHandleType::kInvokeDirect:
+    case DexFile::MethodHandleType::kInvokeInstance:
+    case DexFile::MethodHandleType::kInvokeInterface:
+    case DexFile::MethodHandleType::kInvokeStatic: {
       // TODO(oth): This will not work for varargs methods as this
       // requires instantiating a Transformer. This resolution step
       // would be best done in managed code rather than in the run
       // time (b/35235705)
       Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
       Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referrer->GetClassLoader()));
-      DexFileParameterIterator it(*dex_file, target.method->GetPrototype());
-      for (int32_t i = 0; it.HasNext(); i++, it.Next()) {
+      DexFileParameterIterator it(*dex_file, target_method->GetPrototype());
+      int32_t index = 0;
+      if (handle_type != DexFile::MethodHandleType::kInvokeStatic) {
+        method_params->Set(index++, target_method->GetDeclaringClass());
+      }
+      while (it.HasNext()) {
         const dex::TypeIndex type_idx = it.GetTypeIdx();
         mirror::Class* klass = ResolveType(*dex_file, type_idx, dex_cache, class_loader);
         if (nullptr == klass) {
           DCHECK(self->IsExceptionPending());
           return nullptr;
         }
-        method_params->Set(i, klass);
+        method_params->Set(index++, klass);
+        it.Next();
       }
-      return_type = hs.NewHandle(target.method->GetReturnType(true));
+      return_type = hs.NewHandle(target_method->GetReturnType(true));
       break;
     }
     case DexFile::MethodHandleType::kInvokeConstructor: {
@@ -8388,7 +8469,14 @@
     DCHECK(self->IsExceptionPending());
     return nullptr;
   }
-  return mirror::MethodHandleImpl::Create(self, target.field_or_method, kind, mt);
+
+  uintptr_t target;
+  if (target_field != nullptr) {
+    target = reinterpret_cast<uintptr_t>(target_field);
+  } else {
+    target = reinterpret_cast<uintptr_t>(target_method);
+  }
+  return mirror::MethodHandleImpl::Create(self, target, kind, mt);
 }
 
 bool ClassLinker::IsQuickResolutionStub(const void* entry_point) const {
diff --git a/runtime/dex_file.h b/runtime/dex_file.h
index 81a39af..eb3b210 100644
--- a/runtime/dex_file.h
+++ b/runtime/dex_file.h
@@ -272,7 +272,9 @@
                                   // can be any non-static method on any class (or interface) except
                                   // for “<init>”.
     kInvokeConstructor = 0x0006,  // an invoker for a given constructor.
-    kLast = kInvokeConstructor
+    kInvokeDirect      = 0x0007,  // an invoker for a direct (special) method.
+    kInvokeInterface   = 0x0008,  // an invoker for an interface method.
+    kLast = kInvokeInterface
   };
 
   // raw method_handle_item
diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc
index c18ab47..c5c4eda 100644
--- a/runtime/dex_file_verifier.cc
+++ b/runtime/dex_file_verifier.cc
@@ -2483,7 +2483,9 @@
     }
     case DexFile::MethodHandleType::kInvokeStatic:
     case DexFile::MethodHandleType::kInvokeInstance:
-    case DexFile::MethodHandleType::kInvokeConstructor: {
+    case DexFile::MethodHandleType::kInvokeConstructor:
+    case DexFile::MethodHandleType::kInvokeDirect:
+    case DexFile::MethodHandleType::kInvokeInterface: {
       LOAD_METHOD(method, index, "method_handle_item method_idx", return false);
       break;
     }
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 1b36c3f..b57e2b2 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -640,7 +640,7 @@
   const DexFile* dex_file = referrer->GetDexFile();
   const DexFile::CallSiteIdItem& csi = dex_file->GetCallSiteId(call_site_idx);
 
-  StackHandleScope<9> hs(self);
+  StackHandleScope<10> hs(self);
   Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referrer->GetClassLoader()));
   Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
 
@@ -836,9 +836,13 @@
     return nullptr;
   }
 
-  // Check the target method type matches the method type requested.
-  if (UNLIKELY(!target->GetMethodType()->IsExactMatch(method_type.Get()))) {
-    ThrowWrongMethodTypeException(target->GetMethodType(), method_type.Get());
+  // Check the target method type matches the method type requested modulo the receiver
+  // needs to be compatible rather than exact.
+  Handle<mirror::MethodType> target_method_type = hs.NewHandle(target->GetMethodType());
+  if (UNLIKELY(!target_method_type->IsExactMatch(method_type.Get()) &&
+               !IsParameterTypeConvertible(target_method_type->GetPTypes()->GetWithoutChecks(0),
+                                           method_type->GetPTypes()->GetWithoutChecks(0)))) {
+    ThrowWrongMethodTypeException(target_method_type.Get(), method_type.Get());
     return nullptr;
   }
 
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 9b65255..efb02f6 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -4159,7 +4159,8 @@
   const DexFile::MethodHandleItem& mh = dex_file_->GetMethodHandle(method_handle_idx);
   if (mh.method_handle_type_ != static_cast<uint16_t>(DexFile::MethodHandleType::kInvokeStatic)) {
     Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
-                                      << " argument 0 method handle type is not InvokeStatic";
+                                      << " argument 0 method handle type is not InvokeStatic: "
+                                      << mh.method_handle_type_;
     return false;
   }
 
diff --git a/test/952-invoke-custom-kinds/build b/test/952-invoke-custom-kinds/build
new file mode 100644
index 0000000..a02cdc3
--- /dev/null
+++ b/test/952-invoke-custom-kinds/build
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+# Stop if something fails.
+set -e
+
+${DX} --dex --min-sdk-version=26 --output=classes.dex classes
+
+zip $TEST_NAME.jar classes.dex
diff --git a/test/952-invoke-custom-kinds/classes/Main.class b/test/952-invoke-custom-kinds/classes/Main.class
new file mode 100644
index 0000000..6bc04e3
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/Main.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class b/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class
new file mode 100644
index 0000000..5dfe958
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class b/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class
new file mode 100644
index 0000000..a11ee69
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class
new file mode 100644
index 0000000..e233feb
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class
new file mode 100644
index 0000000..41e1d43
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class
new file mode 100644
index 0000000..4f0f497
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/Super.class b/test/952-invoke-custom-kinds/classes/invokecustom/Super.class
new file mode 100644
index 0000000..5990f28
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/Super.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class
new file mode 100644
index 0000000..c3266e4
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class
new file mode 100644
index 0000000..711db23
--- /dev/null
+++ b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class
Binary files differ
diff --git a/test/952-invoke-custom-kinds/expected.txt b/test/952-invoke-custom-kinds/expected.txt
new file mode 100644
index 0000000..c72da25
--- /dev/null
+++ b/test/952-invoke-custom-kinds/expected.txt
@@ -0,0 +1,38 @@
+bsmLookupStatic []
+Hello World!
+bsmLookupStatic []
+true
+127
+c
+1024
+123456
+1.2
+123456789
+3.5123456789
+String
+bsmLookupStaticWithExtraArgs [1, 123456789, 123.456, 123456.789123]
+targetMethodTest3 from InvokeCustom
+bsmCreateCallSite [MethodHandle(Super)void]
+targetMethodTest4 from Super
+bsmLookupStatic []
+targetMethodTest5 1000 + -923 = 77
+targetMethodTest5 returned: 77
+bsmLookupStatic []
+targetMethodTest6 8209686820727 + -1172812402961 = 7036874417766
+targetMethodTest6 returned: 7036874417766
+bsmLookupStatic []
+targetMethodTest7 0.50097656 * -0.50097656 = -0.2509775161743164
+targetMethodTest6 returned: -0.2509775161743164
+bsmLookupStatic []
+targetMethodTest8 First invokedynamic invocation
+bsmLookupStatic []
+targetMethodTest8 Second invokedynamic invocation
+bsmLookupStatic []
+targetMethodTest8 Dupe first invokedynamic invocation
+bsmLookupTest9 [MethodHandle()int, MethodHandle(int)void, MethodHandle(InvokeCustom)float, MethodHandle(InvokeCustom,float)void]
+targetMethodTest9 ()void
+checkStaticFieldTest9: old 0 new 1985229328 expected 1985229328 OK
+checkFieldTest9: old 0.0 new 1.99E-19 expected 1.99E-19 OK
+helperMethodTest9 in class invokecustom.InvokeCustom
+run() for Test9
+targetMethodTest9()
diff --git a/test/952-invoke-custom-kinds/info.txt b/test/952-invoke-custom-kinds/info.txt
new file mode 100644
index 0000000..cddb96d
--- /dev/null
+++ b/test/952-invoke-custom-kinds/info.txt
@@ -0,0 +1,7 @@
+This test checks call sites and constant method handles in DEX files used
+by invoke-custom.
+
+It is derived from a dx test (dalvik/dx/tests/135-invoke-custom) which
+generates the invoke-custom using ASM to generate class files. The
+test here differs as it not include an instance a constant method
+handle with a constructor kind (see b/62774190).