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).