ART: Update invoke-custom implementation

Adds type checking for bootstrap arguments and support for variable
arity bootstrap arguments.

Adds tests for malformed bootstrap arguments, variable arity bootstrap
arguments, and invocation arguments to passed to the CallSite's
MethodHandle.

Removes unnecessary wrapping when j.l.Error's are raised during BSM
invocation.

Removes BSM argument type checking from verifier. This is now
performed during invocation.

Bug: 73927525
Test: art/test/run-test --host 952-invoke-custom
Change-Id: Id43226edad64ad9812e4ba1a069dfb70b8196dad
diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc
index 8f65c66..7484dd9 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -884,8 +884,8 @@
 
 // WrongMethodTypeException
 
-void ThrowWrongMethodTypeException(mirror::MethodType* expected_type,
-                                   mirror::MethodType* actual_type) {
+void ThrowWrongMethodTypeException(ObjPtr<mirror::MethodType> expected_type,
+                                   ObjPtr<mirror::MethodType> actual_type) {
   ThrowException("Ljava/lang/invoke/WrongMethodTypeException;",
                  nullptr,
                  StringPrintf("Expected %s but was %s",
diff --git a/runtime/common_throws.h b/runtime/common_throws.h
index e9baa4f..29a056e 100644
--- a/runtime/common_throws.h
+++ b/runtime/common_throws.h
@@ -270,8 +270,8 @@
 
 // WrongMethodTypeException
 
-void ThrowWrongMethodTypeException(mirror::MethodType* callee_type,
-                                   mirror::MethodType* callsite_type)
+void ThrowWrongMethodTypeException(ObjPtr<mirror::MethodType> callee_type,
+                                   ObjPtr<mirror::MethodType> callsite_type)
     REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
 
 }  // namespace art
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 380a981..8a85ee4 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -900,171 +900,489 @@
   }
 }
 
+static JValue ConvertScalarBootstrapArgument(jvalue value) {
+  // value either contains a primitive scalar value if it corresponds
+  // to a primitive type, or it contains an integer value if it
+  // corresponds to an object instance reference id (e.g. a string id).
+  return JValue::FromPrimitive(value.j);
+}
+
+static ObjPtr<mirror::Class> GetClassForBootstrapArgument(EncodedArrayValueIterator::ValueType type)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  switch (type) {
+    case EncodedArrayValueIterator::ValueType::kBoolean:
+    case EncodedArrayValueIterator::ValueType::kByte:
+    case EncodedArrayValueIterator::ValueType::kChar:
+    case EncodedArrayValueIterator::ValueType::kShort:
+      // These types are disallowed by JVMS. Treat as integers. This
+      // will result in CCE's being raised if the BSM has one of these
+      // types.
+    case EncodedArrayValueIterator::ValueType::kInt:
+      return class_linker->FindPrimitiveClass('I');
+    case EncodedArrayValueIterator::ValueType::kLong:
+      return class_linker->FindPrimitiveClass('J');
+    case EncodedArrayValueIterator::ValueType::kFloat:
+      return class_linker->FindPrimitiveClass('F');
+    case EncodedArrayValueIterator::ValueType::kDouble:
+      return class_linker->FindPrimitiveClass('D');
+    case EncodedArrayValueIterator::ValueType::kMethodType:
+      return mirror::MethodType::StaticClass();
+    case EncodedArrayValueIterator::ValueType::kMethodHandle:
+      return mirror::MethodHandle::StaticClass();
+    case EncodedArrayValueIterator::ValueType::kString:
+      return mirror::String::GetJavaLangString();
+    case EncodedArrayValueIterator::ValueType::kType:
+      return mirror::Class::GetJavaLangClass();
+    case EncodedArrayValueIterator::ValueType::kField:
+    case EncodedArrayValueIterator::ValueType::kMethod:
+    case EncodedArrayValueIterator::ValueType::kEnum:
+    case EncodedArrayValueIterator::ValueType::kArray:
+    case EncodedArrayValueIterator::ValueType::kAnnotation:
+    case EncodedArrayValueIterator::ValueType::kNull:
+      return nullptr;
+  }
+}
+
+static bool GetArgumentForBootstrapMethod(Thread* self,
+                                          ArtMethod* referrer,
+                                          EncodedArrayValueIterator::ValueType type,
+                                          const JValue* encoded_value,
+                                          JValue* decoded_value)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // The encoded_value contains either a scalar value (IJDF) or a
+  // scalar DEX file index to a reference type to be materialized.
+  switch (type) {
+    case EncodedArrayValueIterator::ValueType::kInt:
+    case EncodedArrayValueIterator::ValueType::kFloat:
+      decoded_value->SetI(encoded_value->GetI());
+      return true;
+    case EncodedArrayValueIterator::ValueType::kLong:
+    case EncodedArrayValueIterator::ValueType::kDouble:
+      decoded_value->SetJ(encoded_value->GetJ());
+      return true;
+    case EncodedArrayValueIterator::ValueType::kMethodType: {
+      StackHandleScope<2> hs(self);
+      Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referrer->GetClassLoader()));
+      Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
+      uint32_t index = static_cast<uint32_t>(encoded_value->GetI());
+      ClassLinker* cl = Runtime::Current()->GetClassLinker();
+      ObjPtr<mirror::MethodType> o = cl->ResolveMethodType(self, index, dex_cache, class_loader);
+      if (UNLIKELY(o.IsNull())) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      decoded_value->SetL(o);
+      return true;
+    }
+    case EncodedArrayValueIterator::ValueType::kMethodHandle: {
+      uint32_t index = static_cast<uint32_t>(encoded_value->GetI());
+      ClassLinker* cl = Runtime::Current()->GetClassLinker();
+      ObjPtr<mirror::MethodHandle> o = cl->ResolveMethodHandle(self, index, referrer);
+      if (UNLIKELY(o.IsNull())) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      decoded_value->SetL(o);
+      return true;
+    }
+    case EncodedArrayValueIterator::ValueType::kString: {
+      StackHandleScope<1> hs(self);
+      Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
+      dex::StringIndex index(static_cast<uint32_t>(encoded_value->GetI()));
+      ClassLinker* cl = Runtime::Current()->GetClassLinker();
+      ObjPtr<mirror::String> o = cl->ResolveString(index, dex_cache);
+      if (UNLIKELY(o.IsNull())) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      decoded_value->SetL(o);
+      return true;
+    }
+    case EncodedArrayValueIterator::ValueType::kType: {
+      StackHandleScope<2> hs(self);
+      Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referrer->GetClassLoader()));
+      Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
+      dex::TypeIndex index(static_cast<uint32_t>(encoded_value->GetI()));
+      ClassLinker* cl = Runtime::Current()->GetClassLinker();
+      ObjPtr<mirror::Class> o = cl->ResolveType(index, dex_cache, class_loader);
+      if (UNLIKELY(o.IsNull())) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      decoded_value->SetL(o);
+      return true;
+    }
+    case EncodedArrayValueIterator::ValueType::kBoolean:
+    case EncodedArrayValueIterator::ValueType::kByte:
+    case EncodedArrayValueIterator::ValueType::kChar:
+    case EncodedArrayValueIterator::ValueType::kShort:
+    case EncodedArrayValueIterator::ValueType::kField:
+    case EncodedArrayValueIterator::ValueType::kMethod:
+    case EncodedArrayValueIterator::ValueType::kEnum:
+    case EncodedArrayValueIterator::ValueType::kArray:
+    case EncodedArrayValueIterator::ValueType::kAnnotation:
+    case EncodedArrayValueIterator::ValueType::kNull:
+      // Unreachable - unsupported types that have been checked when
+      // determining the effect call site type based on the bootstrap
+      // argument types.
+      UNREACHABLE();
+  }
+}
+
+static bool PackArgumentForBootstrapMethod(Thread* self,
+                                           ArtMethod* referrer,
+                                           CallSiteArrayValueIterator* it,
+                                           ShadowFrameSetter* setter)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  auto type = it->GetValueType();
+  const JValue encoded_value = ConvertScalarBootstrapArgument(it->GetJavaValue());
+  JValue decoded_value;
+  if (!GetArgumentForBootstrapMethod(self, referrer, type, &encoded_value, &decoded_value)) {
+    return false;
+  }
+  switch (it->GetValueType()) {
+    case EncodedArrayValueIterator::ValueType::kInt:
+    case EncodedArrayValueIterator::ValueType::kFloat:
+      setter->Set(static_cast<uint32_t>(decoded_value.GetI()));
+      return true;
+    case EncodedArrayValueIterator::ValueType::kLong:
+    case EncodedArrayValueIterator::ValueType::kDouble:
+      setter->SetLong(decoded_value.GetJ());
+      return true;
+    case EncodedArrayValueIterator::ValueType::kMethodType:
+    case EncodedArrayValueIterator::ValueType::kMethodHandle:
+    case EncodedArrayValueIterator::ValueType::kString:
+    case EncodedArrayValueIterator::ValueType::kType:
+      setter->SetReference(decoded_value.GetL());
+      return true;
+    case EncodedArrayValueIterator::ValueType::kBoolean:
+    case EncodedArrayValueIterator::ValueType::kByte:
+    case EncodedArrayValueIterator::ValueType::kChar:
+    case EncodedArrayValueIterator::ValueType::kShort:
+    case EncodedArrayValueIterator::ValueType::kField:
+    case EncodedArrayValueIterator::ValueType::kMethod:
+    case EncodedArrayValueIterator::ValueType::kEnum:
+    case EncodedArrayValueIterator::ValueType::kArray:
+    case EncodedArrayValueIterator::ValueType::kAnnotation:
+    case EncodedArrayValueIterator::ValueType::kNull:
+      // Unreachable - unsupported types that have been checked when
+      // determining the effect call site type based on the bootstrap
+      // argument types.
+      UNREACHABLE();
+  }
+}
+
+static bool PackCollectorArrayForBootstrapMethod(Thread* self,
+                                                 ArtMethod* referrer,
+                                                 ObjPtr<mirror::Class> array_type,
+                                                 int32_t array_length,
+                                                 CallSiteArrayValueIterator* it,
+                                                 ShadowFrameSetter* setter)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<1> hs(self);
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  JValue decoded_value;
+
+#define COLLECT_PRIMITIVE_ARRAY(Descriptor, Type)                       \
+  Handle<mirror::Type ## Array> array =                                 \
+      hs.NewHandle(mirror::Type ## Array::Alloc(self, array_length));   \
+  if (array.IsNull()) {                                                 \
+    return false;                                                       \
+  }                                                                     \
+  for (int32_t i = 0; it->HasNext(); it->Next(), ++i) {                 \
+    auto type = it->GetValueType();                                     \
+    DCHECK_EQ(type, EncodedArrayValueIterator::ValueType::k ## Type);   \
+    const JValue encoded_value =                                        \
+        ConvertScalarBootstrapArgument(it->GetJavaValue());             \
+    GetArgumentForBootstrapMethod(self,                                 \
+                                  referrer,                             \
+                                  type,                                 \
+                                  &encoded_value,                       \
+                                  &decoded_value);                      \
+    array->Set(i, decoded_value.Get ## Descriptor());                   \
+  }                                                                     \
+  setter->SetReference(array.Get());                                    \
+  return true;
+
+#define COLLECT_REFERENCE_ARRAY(T, Type)                                \
+  Handle<mirror::ObjectArray<T>> array =                                \
+      hs.NewHandle(mirror::ObjectArray<T>::Alloc(self,                  \
+                                                 array_type,            \
+                                                 array_length));        \
+  if (array.IsNull()) {                                                 \
+    return false;                                                       \
+  }                                                                     \
+  for (int32_t i = 0; it->HasNext(); it->Next(), ++i) {                 \
+    auto type = it->GetValueType();                                     \
+    DCHECK_EQ(type, EncodedArrayValueIterator::ValueType::k ## Type);   \
+    const JValue encoded_value =                                        \
+        ConvertScalarBootstrapArgument(it->GetJavaValue());             \
+    if (!GetArgumentForBootstrapMethod(self,                            \
+                                       referrer,                        \
+                                       type,                            \
+                                       &encoded_value,                  \
+                                       &decoded_value)) {               \
+      return false;                                                     \
+    }                                                                   \
+    ObjPtr<mirror::Object> o = decoded_value.GetL();                    \
+    if (Runtime::Current()->IsActiveTransaction()) {                    \
+      array->Set<true>(i, ObjPtr<T>::DownCast(o));                      \
+    } else {                                                            \
+      array->Set<false>(i, ObjPtr<T>::DownCast(o));                     \
+    }                                                                   \
+  }                                                                     \
+  setter->SetReference(array.Get());                                    \
+  return true;
+
+  if (array_type->GetComponentType() == class_linker->FindPrimitiveClass('I')) {
+    COLLECT_PRIMITIVE_ARRAY(I, Int);
+  } else if (array_type->GetComponentType() == class_linker->FindPrimitiveClass('J')) {
+    COLLECT_PRIMITIVE_ARRAY(J, Long);
+  } else if (array_type->GetComponentType() == class_linker->FindPrimitiveClass('F')) {
+    COLLECT_PRIMITIVE_ARRAY(F, Float);
+  } else if (array_type->GetComponentType() == class_linker->FindPrimitiveClass('D')) {
+    COLLECT_PRIMITIVE_ARRAY(D, Double);
+  } else if (array_type->GetComponentType() == mirror::MethodType::StaticClass()) {
+    COLLECT_REFERENCE_ARRAY(mirror::MethodType, MethodType);
+  } else if (array_type->GetComponentType() == mirror::MethodHandle::StaticClass()) {
+    COLLECT_REFERENCE_ARRAY(mirror::MethodHandle, MethodHandle);
+  } else if (array_type->GetComponentType() == mirror::String::GetJavaLangString()) {
+    COLLECT_REFERENCE_ARRAY(mirror::String, String);
+  } else if (array_type->GetComponentType() == mirror::Class::GetJavaLangClass()) {
+    COLLECT_REFERENCE_ARRAY(mirror::Class, Type);
+  } else {
+    UNREACHABLE();
+  }
+  #undef COLLECT_PRIMITIVE_ARRAY
+  #undef COLLECT_REFERENCE_ARRAY
+}
+
+static ObjPtr<mirror::MethodType> BuildCallSiteForBootstrapMethod(Thread* self,
+                                                                  const DexFile* dex_file,
+                                                                  uint32_t call_site_idx)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const DexFile::CallSiteIdItem& csi = dex_file->GetCallSiteId(call_site_idx);
+  CallSiteArrayValueIterator it(*dex_file, csi);
+  DCHECK_GE(it.Size(), 1u);
+
+  StackHandleScope<2> hs(self);
+  // Create array for parameter types.
+  ObjPtr<mirror::Class> class_type = mirror::Class::GetJavaLangClass();
+  mirror::Class* class_array_type =
+      Runtime::Current()->GetClassLinker()->FindArrayClass(self, &class_type);
+  Handle<mirror::ObjectArray<mirror::Class>> ptypes = hs.NewHandle(
+      mirror::ObjectArray<mirror::Class>::Alloc(self,
+                                                class_array_type,
+                                                static_cast<int>(it.Size())));
+  if (ptypes.IsNull()) {
+    DCHECK(self->IsExceptionPending());
+    return nullptr;
+  }
+
+  // Populate the first argument with an instance of j.l.i.MethodHandles.Lookup
+  // that the runtime will construct.
+  ptypes->Set(0, mirror::MethodHandlesLookup::StaticClass());
+  it.Next();
+
+  // The remaining parameter types are derived from the types of
+  // arguments present in the DEX file.
+  int index = 1;
+  while (it.HasNext()) {
+    ObjPtr<mirror::Class> ptype = GetClassForBootstrapArgument(it.GetValueType());
+    if (ptype.IsNull()) {
+      ThrowClassCastException("Unsupported bootstrap argument type");
+      return nullptr;
+    }
+    ptypes->Set(index, ptype);
+    index++;
+    it.Next();
+  }
+  DCHECK_EQ(static_cast<size_t>(index), it.Size());
+
+  // By definition, the return type is always a j.l.i.CallSite.
+  Handle<mirror::Class> rtype = hs.NewHandle(mirror::CallSite::StaticClass());
+  return mirror::MethodType::Create(self, rtype, ptypes);
+}
+
 static ObjPtr<mirror::CallSite> InvokeBootstrapMethod(Thread* self,
                                                       ShadowFrame& shadow_frame,
                                                       uint32_t call_site_idx)
     REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<7> hs(self);
+  // There are three mandatory arguments expected from the call site
+  // value array in the DEX file: the bootstrap method handle, the
+  // method name to pass to the bootstrap method, and the method type
+  // to pass to the bootstrap method.
+  static constexpr size_t kMandatoryArgumentsCount = 3;
   ArtMethod* referrer = shadow_frame.GetMethod();
   const DexFile* dex_file = referrer->GetDexFile();
   const DexFile::CallSiteIdItem& csi = dex_file->GetCallSiteId(call_site_idx);
-
-  StackHandleScope<10> hs(self);
-  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referrer->GetClassLoader()));
-  Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
-
   CallSiteArrayValueIterator it(*dex_file, csi);
-  uint32_t method_handle_idx = static_cast<uint32_t>(it.GetJavaValue().i);
+  if (it.Size() < kMandatoryArgumentsCount) {
+    ThrowBootstrapMethodError("Truncated bootstrap arguments (%zu < %zu)",
+                              it.Size(), kMandatoryArgumentsCount);
+    return nullptr;
+  }
+
+  if (it.GetValueType() != EncodedArrayValueIterator::ValueType::kMethodHandle) {
+    ThrowBootstrapMethodError("First bootstrap argument is not a method handle");
+    return nullptr;
+  }
+
+  uint32_t bsm_index = static_cast<uint32_t>(it.GetJavaValue().i);
+  it.Next();
+
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  Handle<mirror::MethodHandle>
-      bootstrap(hs.NewHandle(class_linker->ResolveMethodHandle(self, method_handle_idx, referrer)));
-  if (bootstrap.IsNull()) {
+  Handle<mirror::MethodHandle> bsm =
+      hs.NewHandle(class_linker->ResolveMethodHandle(self, bsm_index, referrer));
+  if (bsm.IsNull()) {
     DCHECK(self->IsExceptionPending());
     return nullptr;
   }
-  Handle<mirror::MethodType> bootstrap_method_type = hs.NewHandle(bootstrap->GetMethodType());
-  it.Next();
 
-  DCHECK_EQ(static_cast<size_t>(bootstrap->GetMethodType()->GetPTypes()->GetLength()), it.Size());
-  const size_t num_bootstrap_vregs = bootstrap->GetMethodType()->NumberOfVRegs();
+  if (bsm->GetHandleKind() != mirror::MethodHandle::Kind::kInvokeStatic) {
+    // JLS suggests also accepting constructors. This is currently
+    // hard as constructor invocations happen via transformers in ART
+    // today. The constructor would need to be a class derived from java.lang.invoke.CallSite.
+    ThrowBootstrapMethodError("Unsupported bootstrap method invocation kind");
+    return nullptr;
+  }
+
+  // Construct the local call site type information based on the 3
+  // mandatory arguments provided by the runtime and the static arguments
+  // in the DEX file. We will use these arguments to build a shadow frame.
+  MutableHandle<mirror::MethodType> call_site_type =
+      hs.NewHandle(BuildCallSiteForBootstrapMethod(self, dex_file, call_site_idx));
+  if (call_site_type.IsNull()) {
+    DCHECK(self->IsExceptionPending());
+    return nullptr;
+  }
+
+  // Check if this BSM is targeting a variable arity method. If so,
+  // we'll need to collect the trailing arguments into an array.
+  Handle<mirror::Array> collector_arguments;
+  int32_t collector_arguments_length;
+  if (bsm->GetTargetMethod()->IsVarargs()) {
+    int number_of_bsm_parameters = bsm->GetMethodType()->GetNumberOfPTypes();
+    if (number_of_bsm_parameters == 0) {
+      ThrowBootstrapMethodError("Variable arity BSM does not have any arguments");
+      return nullptr;
+    }
+    Handle<mirror::Class> collector_array_class =
+        hs.NewHandle(bsm->GetMethodType()->GetPTypes()->Get(number_of_bsm_parameters - 1));
+    if (!collector_array_class->IsArrayClass()) {
+      ThrowBootstrapMethodError("Variable arity BSM does not have array as final argument");
+      return nullptr;
+    }
+    // The call site may include no arguments to be collected. In this
+    // case the number of arguments must be at least the number of BSM
+    // parameters less the collector array.
+    if (call_site_type->GetNumberOfPTypes() < number_of_bsm_parameters - 1) {
+      ThrowWrongMethodTypeException(bsm->GetMethodType(), call_site_type.Get());
+      return nullptr;
+    }
+    // Check all the arguments to be collected match the collector array component type.
+    for (int i = number_of_bsm_parameters - 1; i < call_site_type->GetNumberOfPTypes(); ++i) {
+      if (call_site_type->GetPTypes()->Get(i) != collector_array_class->GetComponentType()) {
+        ThrowClassCastException(collector_array_class->GetComponentType(),
+                                call_site_type->GetPTypes()->Get(i));
+        return nullptr;
+      }
+    }
+    // Update the call site method type so it now includes the collector array.
+    int32_t collector_arguments_start = number_of_bsm_parameters - 1;
+    collector_arguments_length = call_site_type->GetNumberOfPTypes() - number_of_bsm_parameters + 1;
+    call_site_type.Assign(
+        mirror::MethodType::CollectTrailingArguments(self,
+                                                     call_site_type.Get(),
+                                                     collector_array_class.Get(),
+                                                     collector_arguments_start));
+    if (call_site_type.IsNull()) {
+      DCHECK(self->IsExceptionPending());
+      return nullptr;
+    }
+  } else {
+    collector_arguments_length = 0;
+  }
+
+  if (call_site_type->GetNumberOfPTypes() != bsm->GetMethodType()->GetNumberOfPTypes()) {
+    ThrowWrongMethodTypeException(bsm->GetMethodType(), call_site_type.Get());
+    return nullptr;
+  }
+
+  // BSM invocation has a different set of exceptions that
+  // j.l.i.MethodHandle.invoke(). Scan arguments looking for CCE
+  // "opportunities". Unfortunately we cannot just leave this to the
+  // method handle invocation as this might generate a WMTE.
+  for (int32_t i = 0; i < call_site_type->GetNumberOfPTypes(); ++i) {
+    ObjPtr<mirror::Class> from = call_site_type->GetPTypes()->Get(i);
+    ObjPtr<mirror::Class> to = bsm->GetMethodType()->GetPTypes()->Get(i);
+    if (!IsParameterTypeConvertible(from, to)) {
+      ThrowClassCastException(from, to);
+      return nullptr;
+    }
+  }
+  if (!IsReturnTypeConvertible(call_site_type->GetRType(), bsm->GetMethodType()->GetRType())) {
+    ThrowClassCastException(bsm->GetMethodType()->GetRType(), call_site_type->GetRType());
+    return nullptr;
+  }
 
   // Set-up a shadow frame for invoking the bootstrap method handle.
   ShadowFrameAllocaUniquePtr bootstrap_frame =
-      CREATE_SHADOW_FRAME(num_bootstrap_vregs, nullptr, referrer, shadow_frame.GetDexPC());
+      CREATE_SHADOW_FRAME(call_site_type->NumberOfVRegs(),
+                          nullptr,
+                          referrer,
+                          shadow_frame.GetDexPC());
   ScopedStackedShadowFramePusher pusher(
       self, bootstrap_frame.get(), StackedShadowFrameType::kShadowFrameUnderConstruction);
-  size_t vreg = 0;
+  ShadowFrameSetter setter(bootstrap_frame.get(), 0u);
 
   // The first parameter is a MethodHandles lookup instance.
-  {
-    Handle<mirror::Class> lookup_class =
-        hs.NewHandle(shadow_frame.GetMethod()->GetDeclaringClass());
-    ObjPtr<mirror::MethodHandlesLookup> lookup =
-        mirror::MethodHandlesLookup::Create(self, lookup_class);
-    if (lookup.IsNull()) {
-      DCHECK(self->IsExceptionPending());
-      return nullptr;
-    }
-    bootstrap_frame->SetVRegReference(vreg++, lookup.Ptr());
-  }
-
-  // The second parameter is the name to lookup.
-  {
-    dex::StringIndex name_idx(static_cast<uint32_t>(it.GetJavaValue().i));
-    ObjPtr<mirror::String> name = class_linker->ResolveString(name_idx, dex_cache);
-    if (name.IsNull()) {
-      DCHECK(self->IsExceptionPending());
-      return nullptr;
-    }
-    bootstrap_frame->SetVRegReference(vreg++, name.Ptr());
-  }
-  it.Next();
-
-  // The third parameter is the method type associated with the name.
-  uint32_t method_type_idx = static_cast<uint32_t>(it.GetJavaValue().i);
-  Handle<mirror::MethodType> method_type(hs.NewHandle(
-      class_linker->ResolveMethodType(self, method_type_idx, dex_cache, class_loader)));
-  if (method_type.IsNull()) {
+  Handle<mirror::Class> lookup_class =
+      hs.NewHandle(shadow_frame.GetMethod()->GetDeclaringClass());
+  ObjPtr<mirror::MethodHandlesLookup> lookup =
+      mirror::MethodHandlesLookup::Create(self, lookup_class);
+  if (lookup.IsNull()) {
     DCHECK(self->IsExceptionPending());
     return nullptr;
   }
-  bootstrap_frame->SetVRegReference(vreg++, method_type.Get());
-  it.Next();
+  setter.SetReference(lookup);
 
-  // Append remaining arguments (if any).
-  while (it.HasNext()) {
-    const jvalue& jvalue = it.GetJavaValue();
-    switch (it.GetValueType()) {
-      case EncodedArrayValueIterator::ValueType::kBoolean:
-      case EncodedArrayValueIterator::ValueType::kByte:
-      case EncodedArrayValueIterator::ValueType::kChar:
-      case EncodedArrayValueIterator::ValueType::kShort:
-      case EncodedArrayValueIterator::ValueType::kInt:
-        bootstrap_frame->SetVReg(vreg, jvalue.i);
-        vreg += 1;
-        break;
-      case EncodedArrayValueIterator::ValueType::kLong:
-        bootstrap_frame->SetVRegLong(vreg, jvalue.j);
-        vreg += 2;
-        break;
-      case EncodedArrayValueIterator::ValueType::kFloat:
-        bootstrap_frame->SetVRegFloat(vreg, jvalue.f);
-        vreg += 1;
-        break;
-      case EncodedArrayValueIterator::ValueType::kDouble:
-        bootstrap_frame->SetVRegDouble(vreg, jvalue.d);
-        vreg += 2;
-        break;
-      case EncodedArrayValueIterator::ValueType::kMethodType: {
-        uint32_t idx = static_cast<uint32_t>(jvalue.i);
-        ObjPtr<mirror::MethodType> ref =
-            class_linker->ResolveMethodType(self, idx, dex_cache, class_loader);
-        if (ref.IsNull()) {
-          DCHECK(self->IsExceptionPending());
-          return nullptr;
-        }
-        bootstrap_frame->SetVRegReference(vreg, ref.Ptr());
-        vreg += 1;
-        break;
+  // Pack the remaining arguments into the frame.
+  int number_of_arguments = call_site_type->GetNumberOfPTypes();
+  int argument_index;
+  for (argument_index = 1; argument_index < number_of_arguments; ++argument_index) {
+    if (argument_index == number_of_arguments - 1 &&
+        call_site_type->GetPTypes()->Get(argument_index)->IsArrayClass()) {
+      ObjPtr<mirror::Class> array_type = call_site_type->GetPTypes()->Get(argument_index);
+      if (!PackCollectorArrayForBootstrapMethod(self,
+                                                referrer,
+                                                array_type,
+                                                collector_arguments_length,
+                                                &it,
+                                                &setter)) {
+        DCHECK(self->IsExceptionPending());
+        return nullptr;
       }
-      case EncodedArrayValueIterator::ValueType::kMethodHandle: {
-        uint32_t idx = static_cast<uint32_t>(jvalue.i);
-        ObjPtr<mirror::MethodHandle> ref =
-            class_linker->ResolveMethodHandle(self, idx, referrer);
-        if (ref.IsNull()) {
-          DCHECK(self->IsExceptionPending());
-          return nullptr;
-        }
-        bootstrap_frame->SetVRegReference(vreg, ref.Ptr());
-        vreg += 1;
-        break;
-      }
-      case EncodedArrayValueIterator::ValueType::kString: {
-        dex::StringIndex idx(static_cast<uint32_t>(jvalue.i));
-        ObjPtr<mirror::String> ref = class_linker->ResolveString(idx, dex_cache);
-        if (ref.IsNull()) {
-          DCHECK(self->IsExceptionPending());
-          return nullptr;
-        }
-        bootstrap_frame->SetVRegReference(vreg, ref.Ptr());
-        vreg += 1;
-        break;
-      }
-      case EncodedArrayValueIterator::ValueType::kType: {
-        dex::TypeIndex idx(static_cast<uint32_t>(jvalue.i));
-        ObjPtr<mirror::Class> ref = class_linker->ResolveType(idx, dex_cache, class_loader);
-        if (ref.IsNull()) {
-          DCHECK(self->IsExceptionPending());
-          return nullptr;
-        }
-        bootstrap_frame->SetVRegReference(vreg, ref.Ptr());
-        vreg += 1;
-        break;
-      }
-      case EncodedArrayValueIterator::ValueType::kNull:
-        bootstrap_frame->SetVRegReference(vreg, nullptr);
-        vreg += 1;
-        break;
-      case EncodedArrayValueIterator::ValueType::kField:
-      case EncodedArrayValueIterator::ValueType::kMethod:
-      case EncodedArrayValueIterator::ValueType::kEnum:
-      case EncodedArrayValueIterator::ValueType::kArray:
-      case EncodedArrayValueIterator::ValueType::kAnnotation:
-        // Unreachable based on current EncodedArrayValueIterator::Next().
-        UNREACHABLE();
+    } else if (!PackArgumentForBootstrapMethod(self, referrer, &it, &setter)) {
+      DCHECK(self->IsExceptionPending());
+      return nullptr;
     }
-
     it.Next();
   }
+  DCHECK(!it.HasNext());
+  DCHECK(setter.Done());
 
   // Invoke the bootstrap method handle.
   JValue result;
-  RangeInstructionOperands operands(0, vreg);
-  bool invoke_success = MethodHandleInvokeExact(self,
-                                                *bootstrap_frame,
-                                                bootstrap,
-                                                bootstrap_method_type,
-                                                &operands,
-                                                &result);
+  RangeInstructionOperands operands(0, bootstrap_frame->NumberOfVRegs());
+  bool invoke_success = MethodHandleInvoke(self,
+                                           *bootstrap_frame,
+                                           bsm,
+                                           call_site_type,
+                                           &operands,
+                                           &result);
   if (!invoke_success) {
     DCHECK(self->IsExceptionPending());
     return nullptr;
@@ -1077,31 +1395,20 @@
     return nullptr;
   }
 
-  // Check the result type is a subclass of CallSite.
+  // Check the result type is a subclass of j.l.i.CallSite.
   if (UNLIKELY(!object->InstanceOf(mirror::CallSite::StaticClass()))) {
     ThrowClassCastException(object->GetClass(), mirror::CallSite::StaticClass());
     return nullptr;
   }
 
+  // Check the call site target is not null as we're going to invoke it.
   Handle<mirror::CallSite> call_site =
       hs.NewHandle(ObjPtr<mirror::CallSite>::DownCast(ObjPtr<mirror::Object>(result.GetL())));
-  // Check the call site target is not null as we're going to invoke it.
   Handle<mirror::MethodHandle> target = hs.NewHandle(call_site->GetTarget());
   if (UNLIKELY(target.IsNull())) {
-    ThrowClassCastException("Bootstrap method did not return a callsite");
+    ThrowClassCastException("Bootstrap method returned a CallSite with a null target");
     return nullptr;
   }
-
-  // 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;
-  }
-
   return call_site.Get();
 }
 
@@ -1129,8 +1436,11 @@
     call_site.Assign(InvokeBootstrapMethod(self, shadow_frame, call_site_idx));
     if (UNLIKELY(call_site.IsNull())) {
       CHECK(self->IsExceptionPending());
-      ThrowWrappedBootstrapMethodError("Exception from call site #%u bootstrap method",
-                                       call_site_idx);
+      if (!self->GetException()->IsError()) {
+        // Use a BootstrapMethodError if the exception is not an instance of java.lang.Error.
+        ThrowWrappedBootstrapMethodError("Exception from call site #%u bootstrap method",
+                                         call_site_idx);
+      }
       result->SetJ(0);
       return false;
     }
@@ -1139,9 +1449,6 @@
     call_site.Assign(winning_call_site);
   }
 
-  // CallSite.java checks the re-assignment of the call site target
-  // when mutating call site targets. We only check the target is
-  // non-null and has the right type during bootstrap method execution.
   Handle<mirror::MethodHandle> target = hs.NewHandle(call_site->GetTarget());
   Handle<mirror::MethodType> target_method_type = hs.NewHandle(target->GetMethodType());
   DCHECK_EQ(static_cast<size_t>(inst->VRegA()), target_method_type->NumberOfVRegs());
diff --git a/runtime/method_handles.h b/runtime/method_handles.h
index 7e60a5c..fce3d06 100644
--- a/runtime/method_handles.h
+++ b/runtime/method_handles.h
@@ -174,19 +174,26 @@
       : shadow_frame_(shadow_frame), arg_index_(first_dst_reg) {}
 
   ALWAYS_INLINE void Set(uint32_t value) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_LT(arg_index_, shadow_frame_->NumberOfVRegs());
     shadow_frame_->SetVReg(arg_index_++, value);
   }
 
   ALWAYS_INLINE void SetReference(ObjPtr<mirror::Object> value)
       REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_LT(arg_index_, shadow_frame_->NumberOfVRegs());
     shadow_frame_->SetVRegReference(arg_index_++, value.Ptr());
   }
 
   ALWAYS_INLINE void SetLong(int64_t value) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_LT(arg_index_, shadow_frame_->NumberOfVRegs());
     shadow_frame_->SetVRegLong(arg_index_, value);
     arg_index_ += 2;
   }
 
+  ALWAYS_INLINE bool Done() const {
+    return arg_index_ == shadow_frame_->NumberOfVRegs();
+  }
+
  private:
   ShadowFrame* shadow_frame_;
   size_t arg_index_;
diff --git a/runtime/mirror/method_type.cc b/runtime/mirror/method_type.cc
index 6ac5012..45f7a87 100644
--- a/runtime/mirror/method_type.cc
+++ b/runtime/mirror/method_type.cc
@@ -23,6 +23,18 @@
 namespace art {
 namespace mirror {
 
+namespace {
+
+ObjPtr<ObjectArray<Class>> AllocatePTypesArray(Thread* self, int count)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<Class> class_type = Class::GetJavaLangClass();
+  ObjPtr<Class> class_array_type =
+      Runtime::Current()->GetClassLinker()->FindArrayClass(self, &class_type);
+  return ObjectArray<Class>::Alloc(self, class_array_type, count);
+}
+
+}  // namespace
+
 GcRoot<Class> MethodType::static_class_;
 
 MethodType* MethodType::Create(Thread* const self,
@@ -47,18 +59,41 @@
 MethodType* MethodType::CloneWithoutLeadingParameter(Thread* const self,
                                                      ObjPtr<MethodType> method_type) {
   StackHandleScope<3> hs(self);
-  Handle<Class> rtype = hs.NewHandle(method_type->GetRType());
   Handle<ObjectArray<Class>> src_ptypes = hs.NewHandle(method_type->GetPTypes());
-  ObjPtr<Class> class_type = Class::GetJavaLangClass();
-  ObjPtr<Class> class_array_type =
-      Runtime::Current()->GetClassLinker()->FindArrayClass(self, &class_type);
-  const int32_t dst_ptypes_count = src_ptypes->GetLength() - 1;
-  Handle<ObjectArray<Class>> dst_ptypes = hs.NewHandle(
-      ObjectArray<Class>::Alloc(self, class_array_type, dst_ptypes_count));
+  Handle<Class> dst_rtype = hs.NewHandle(method_type->GetRType());
+  const int32_t dst_ptypes_count = method_type->GetNumberOfPTypes() - 1;
+  Handle<ObjectArray<Class>> dst_ptypes = hs.NewHandle(AllocatePTypesArray(self, dst_ptypes_count));
+  if (dst_ptypes.IsNull()) {
+    return nullptr;
+  }
   for (int32_t i = 0; i < dst_ptypes_count; ++i) {
     dst_ptypes->Set(i, src_ptypes->Get(i + 1));
   }
-  return Create(self, rtype, dst_ptypes);
+  return Create(self, dst_rtype, dst_ptypes);
+}
+
+MethodType* MethodType::CollectTrailingArguments(Thread* self,
+                                                 ObjPtr<MethodType> method_type,
+                                                 ObjPtr<Class> collector_array_class,
+                                                 int32_t start_index) {
+  int32_t ptypes_length = method_type->GetNumberOfPTypes();
+  if (start_index > ptypes_length) {
+    return method_type.Ptr();
+  }
+
+  StackHandleScope<4> hs(self);
+  Handle<Class> collector_class = hs.NewHandle(collector_array_class);
+  Handle<Class> dst_rtype = hs.NewHandle(method_type->GetRType());
+  Handle<ObjectArray<Class>> src_ptypes = hs.NewHandle(method_type->GetPTypes());
+  Handle<ObjectArray<Class>> dst_ptypes = hs.NewHandle(AllocatePTypesArray(self, start_index + 1));
+  if (dst_ptypes.IsNull()) {
+    return nullptr;
+  }
+  for (int32_t i = 0; i < start_index; ++i) {
+    dst_ptypes->Set(i, src_ptypes->Get(i));
+  }
+  dst_ptypes->Set(start_index, collector_class.Get());
+  return Create(self, dst_rtype, dst_ptypes);
 }
 
 size_t MethodType::NumberOfVRegs() REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/mirror/method_type.h b/runtime/mirror/method_type.h
index 3627214..771162a 100644
--- a/runtime/mirror/method_type.h
+++ b/runtime/mirror/method_type.h
@@ -40,6 +40,14 @@
                                                   ObjPtr<MethodType> method_type)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Collects trailing parameter types into an array. Assumes caller
+  // has checked trailing arguments are all of the same type.
+  static MethodType* CollectTrailingArguments(Thread* const self,
+                                              ObjPtr<MethodType> method_type,
+                                              ObjPtr<Class> collector_array_class,
+                                              int32_t start_index)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   static Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_) {
     return static_class_.Read();
   }
diff --git a/runtime/mirror/throwable.cc b/runtime/mirror/throwable.cc
index b6173d4..7006973 100644
--- a/runtime/mirror/throwable.cc
+++ b/runtime/mirror/throwable.cc
@@ -75,6 +75,10 @@
   return !InstanceOf(WellKnownClasses::ToClass(WellKnownClasses::java_lang_RuntimeException));
 }
 
+bool Throwable::IsError() {
+  return InstanceOf(WellKnownClasses::ToClass(WellKnownClasses::java_lang_Error));
+}
+
 int32_t Throwable::GetStackDepth() {
   ObjPtr<Object> stack_state = GetStackState();
   if (stack_state == nullptr || !stack_state->IsObjectArray()) {
diff --git a/runtime/mirror/throwable.h b/runtime/mirror/throwable.h
index fb45228..b901ca2 100644
--- a/runtime/mirror/throwable.h
+++ b/runtime/mirror/throwable.h
@@ -44,6 +44,7 @@
   void SetCause(ObjPtr<Throwable> cause) REQUIRES_SHARED(Locks::mutator_lock_);
   void SetStackState(ObjPtr<Object> state) REQUIRES_SHARED(Locks::mutator_lock_);
   bool IsCheckedException() REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsError() REQUIRES_SHARED(Locks::mutator_lock_);
 
   static Class* GetJavaLangThrowable() REQUIRES_SHARED(Locks::mutator_lock_) {
     DCHECK(!java_lang_Throwable_.IsNull());
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 74c2244..21a4ecc 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -4206,14 +4206,19 @@
   if (it.Size() < 3) {
     Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
                                       << " has too few arguments: "
-                                      << it.Size() << "< 3";
+                                      << it.Size() << " < 3";
     return false;
   }
 
   // Get and check the first argument: the method handle (index range
   // checked by the dex file verifier).
   uint32_t method_handle_idx = static_cast<uint32_t>(it.GetJavaValue().i);
-  it.Next();
+  if (method_handle_idx > dex_file_->NumMethodHandles()) {
+    Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site id #" << call_site_idx
+                                      << " method handle index invalid " << method_handle_idx
+                                      << " >= "  << dex_file_->NumMethodHandles();
+    return false;
+  }
 
   const DexFile::MethodHandleItem& mh = dex_file_->GetMethodHandle(method_handle_idx);
   if (mh.method_handle_type_ != static_cast<uint16_t>(DexFile::MethodHandleType::kInvokeStatic)) {
@@ -4222,93 +4227,6 @@
                                       << mh.method_handle_type_;
     return false;
   }
-
-  // Skip the second argument, the name to resolve, as checked by the
-  // dex file verifier.
-  it.Next();
-
-  // Skip the third argument, the method type expected, as checked by
-  // the dex file verifier.
-  it.Next();
-
-  // Check the bootstrap method handle and remaining arguments.
-  const DexFile::MethodId& method_id = dex_file_->GetMethodId(mh.field_or_method_idx_);
-  uint32_t length;
-  const char* shorty = dex_file_->GetMethodShorty(method_id, &length);
-
-  if (it.Size() < length - 1) {
-    Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
-                                      << " too few arguments for bootstrap method: "
-                                      << it.Size() << " < " << (length - 1);
-    return false;
-  }
-
-  // Check the return type and first 3 arguments are references
-  // (CallSite, Lookup, String, MethodType). If they are not of the
-  // expected types (or subtypes), it will trigger a
-  // WrongMethodTypeException during execution.
-  if (shorty[0] != 'L') {
-    Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
-                                      << " bootstrap return type is not a reference";
-    return false;
-  }
-
-  for (uint32_t i = 1; i < 4; ++i) {
-    if (shorty[i] != 'L') {
-      Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
-                                        << " bootstrap method argument " << (i - 1)
-                                        << " is not a reference";
-      return false;
-    }
-  }
-
-  // Check the optional arguments.
-  for (uint32_t i = 4; i < length; ++i, it.Next()) {
-    bool match = false;
-    switch (it.GetValueType()) {
-      case EncodedArrayValueIterator::ValueType::kBoolean:
-      case EncodedArrayValueIterator::ValueType::kByte:
-      case EncodedArrayValueIterator::ValueType::kShort:
-      case EncodedArrayValueIterator::ValueType::kChar:
-      case EncodedArrayValueIterator::ValueType::kInt:
-        // These all fit within one register and encoders do not seem
-        // too exacting on the encoding type they use (ie using
-        // integer for all of these).
-        match = (strchr("ZBCSI", shorty[i]) != nullptr);
-        break;
-      case EncodedArrayValueIterator::ValueType::kLong:
-        match = ('J' == shorty[i]);
-        break;
-      case EncodedArrayValueIterator::ValueType::kFloat:
-        match = ('F' == shorty[i]);
-        break;
-      case EncodedArrayValueIterator::ValueType::kDouble:
-        match = ('D' == shorty[i]);
-        break;
-      case EncodedArrayValueIterator::ValueType::kMethodType:
-      case EncodedArrayValueIterator::ValueType::kMethodHandle:
-      case EncodedArrayValueIterator::ValueType::kString:
-      case EncodedArrayValueIterator::ValueType::kType:
-      case EncodedArrayValueIterator::ValueType::kNull:
-        match = ('L' == shorty[i]);
-        break;
-      case EncodedArrayValueIterator::ValueType::kField:
-      case EncodedArrayValueIterator::ValueType::kMethod:
-      case EncodedArrayValueIterator::ValueType::kEnum:
-      case EncodedArrayValueIterator::ValueType::kArray:
-      case EncodedArrayValueIterator::ValueType::kAnnotation:
-        // Unreachable based on current EncodedArrayValueIterator::Next().
-        UNREACHABLE();
-    }
-
-    if (!match) {
-      Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
-                                        << " bootstrap method argument " << (i - 1)
-                                        << " expected " << shorty[i]
-                                        << " got value type: " << it.GetValueType();
-      return false;
-    }
-  }
   return true;
 }
 
diff --git a/test/952-invoke-custom/expected.txt b/test/952-invoke-custom/expected.txt
index 767cc7e..7e8ffa6 100644
--- a/test/952-invoke-custom/expected.txt
+++ b/test/952-invoke-custom/expected.txt
@@ -18,3 +18,67 @@
 testInstanceFieldAccessors
 testInvokeVirtual => max(77, -3) = 77
 testConstructor => class TestInvocationKinds$Widget
+TestDynamicArguments
+bsm
+0, One, 3.141592653589793
+bsm
+1, Two, 2.718281828459045
+bsm
+2, Three, 0.0
+0, One, 3.141592653589793
+1, Two, 2.718281828459045
+2, Three, 0.0
+TestBadBootstrapArguments
+bsm(class TestBadBootstrapArguments, happy, ()void, -1, very)
+happy
+invokeWrongParameterTypes => class java.lang.NoSuchMethodError
+invokeMissingParameterTypes => class java.lang.NoSuchMethodError
+invokeExtraArguments => class java.lang.BootstrapMethodError => class java.lang.invoke.WrongMethodTypeException
+invokeWrongArguments => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+invokeWrongArguments => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+invokeWrongArgumentsAgain => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+invokeNarrowArguments => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+bsmDJ(..., 1.7976931348623157E308, 2147483647)
+wideningArguments
+bsmDoubleLong(..., 1.7976931348623157E308, 9223372036854775807)
+boxingArguments
+invokeWideningBoxingArguments => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+bsm returning void value.
+invokeVoidReturnType() => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+bsm returning Object value.
+invokeObjectReturnType() => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+bsm returning Integer value.
+invokeIntegerReturnType() => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+Hello!
+bsmWithStringArray(TestVariableArityLinkerMethod, methodA, ()void, [Aachen, Aalborg, Aalto]);
+methodA
+bsmWithStringArray(TestVariableArityLinkerMethod, methodB, ()void, [barium]);
+methodB
+bsmWithStringArray(TestVariableArityLinkerMethod, methodC, ()void, []);
+methodC
+methodA
+methodB
+methodC
+bsmWithIntAndStringArray(TestVariableArityLinkerMethod, methodD, ()void, 101, [zoo, zoogene, zoogenic]);
+methodD
+bsmWithIntAndStringArray(TestVariableArityLinkerMethod, methodE, ()void, 102, [zonic]);
+methodE
+bsmWithIntAndStringArray(TestVariableArityLinkerMethod, methodF, ()void, 103, []);
+methodF
+methodD
+methodE
+methodF
+bsmWithLongAndIntArray(TestVariableArityLinkerMethod, methodG, ()void, 81985529216486895, [1, -1, 2, -2]);
+methodG
+bsmWithFloatAndLongArray(TestVariableArityLinkerMethod, methodH, ()void, -2.7182817, [999999999999, -8888888888888]);
+methodH
+bsmWithClassAndFloatArray(TestVariableArityLinkerMethod, methodI, ()void, class java.lang.Throwable, [3.4028235E38, 1.4E-45, 3.1415927, -3.1415927]);
+methodI
+bsmWithDoubleArray(TestVariableArityLinkerMethod, methodJ, ()void, [1.7976931348623157E308, 4.9E-324, 2.718281828459045, -3.141592653589793]);
+methodJ
+bsmWithClassArray(TestVariableArityLinkerMethod, methodK, ()void, [class java.lang.Integer, class java.lang.invoke.MethodHandles, class java.util.Arrays]);
+methodK
+methodO => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+methodP => class java.lang.BootstrapMethodError => class java.lang.ClassCastException
+methodQ => class java.lang.BootstrapMethodError => class java.lang.invoke.WrongMethodTypeException
+methodR => class java.lang.BootstrapMethodError => class java.lang.invoke.WrongMethodTypeException
diff --git a/test/952-invoke-custom/src/Main.java b/test/952-invoke-custom/src/Main.java
index 0b1c1ff..d2250a9 100644
--- a/test/952-invoke-custom/src/Main.java
+++ b/test/952-invoke-custom/src/Main.java
@@ -74,18 +74,15 @@
                 TestLinkerMethodMinimalArguments.FAILURE_TYPE_NONE, 10, 13);
     }
 
-    private static void TestInvokeCustomWithConcurrentThreads() throws Throwable {
-        // This is a concurrency test that attempts to run invoke-custom on the same
-        // call site.
-        TestInvokeCustomWithConcurrentThreads.test();
-    }
-
     public static void main(String[] args) throws Throwable {
         TestUninitializedCallSite();
         TestLinkerMethodMinimalArguments();
         TestLinkerMethodMultipleArgumentTypes();
         TestLinkerUnrelatedBSM.test();
-        TestInvokeCustomWithConcurrentThreads();
+        TestInvokeCustomWithConcurrentThreads.test();
         TestInvocationKinds.test();
+        TestDynamicBootstrapArguments.test();
+        TestBadBootstrapArguments.test();
+        TestVariableArityLinkerMethod.test();
     }
 }
diff --git a/test/952-invoke-custom/src/TestBadBootstrapArguments.java b/test/952-invoke-custom/src/TestBadBootstrapArguments.java
new file mode 100644
index 0000000..25d8b59
--- /dev/null
+++ b/test/952-invoke-custom/src/TestBadBootstrapArguments.java
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import annotations.BootstrapMethod;
+import annotations.CalledByIndy;
+import annotations.Constant;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.WrongMethodTypeException;
+
+public class TestBadBootstrapArguments extends TestBase {
+    private static CallSite bsm(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            int extraInt,
+            String extraString)
+            throws Throwable {
+        System.out.print("bsm(");
+        System.out.print(lookup.lookupClass());
+        System.out.print(", ");
+        System.out.print(methodName);
+        System.out.print(", ");
+        System.out.print(methodType);
+        System.out.print(", ");
+        System.out.print(extraInt);
+        System.out.print(", ");
+        System.out.print(extraString);
+        System.out.println(")");
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsm",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String.class
+                    }
+                ),
+        fieldOrMethodName = "happy",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(intValue = -1),
+            @Constant(stringValue = "very")
+        }
+    )
+    private static void invokeHappy() {
+        assertNotReached();
+    }
+
+    private static void happy() {
+        System.out.println("happy");
+    }
+
+    // BootstrapMethod.parameterTypes != parameterTypesOf(constantArgumentsForBootstrapMethod)
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsm",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        double.class
+                    }
+                ),
+        fieldOrMethodName = "wrongParameterTypes",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(intValue = -1),
+            @Constant(stringValue = "very")
+        }
+    )
+    private static void invokeWrongParameterTypes() throws NoSuchMethodError {
+        assertNotReached();
+    }
+
+    private static void wrongParameterTypes() {
+        System.out.println("wrongParameterTypes");
+    }
+
+    // BootstrapMethod.parameterTypes != parameterTypesOf(constantArgumentsForBootstrapMethod)
+    // (missing constantArgumentTypes))
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsm",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        double.class
+                    }
+                ),
+        fieldOrMethodName = "missingParameterTypes",
+        constantArgumentsForBootstrapMethod = {}
+    )
+    private static void invokeMissingParameterTypes() throws NoSuchMethodError {
+        assertNotReached();
+    }
+
+    private static void missingParameterTypes() {
+        System.out.println("missingParameterTypes");
+    }
+
+    // BootstrapMethod.parameterTypes != parameterTypesOf(constantArgumentsForBootstrapMethod):
+    // extra constant present
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsm",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String.class
+                    }
+                ),
+        fieldOrMethodName = "extraArguments",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(intValue = 1),
+            @Constant(stringValue = "2"),
+            @Constant(intValue = 3)
+        }
+    )
+    private static void invokeExtraArguments() {
+        assertNotReached();
+    }
+
+    private static void extraArguments() {
+        System.out.println("extraArguments");
+    }
+
+    // constantArgumentTypes do not correspond to expected parameter types
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsm",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String.class
+                    }
+                ),
+        fieldOrMethodName = "wrongArguments",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(stringValue = "1"),
+            @Constant(doubleValue = Math.PI)
+        }
+    )
+    private static void invokeWrongArguments() {
+        assertNotReached();
+    }
+
+    private static void wrongArguments() {
+        System.out.println("wrongArguments");
+    }
+
+    // constantArgumentTypes do not correspond to expected parameter types
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsm",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String.class
+                    }
+                ),
+        fieldOrMethodName = "wrongArgumentsAgain",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(doubleValue = Math.PI),
+            @Constant(stringValue = "pie")
+        }
+    )
+    private static void invokeWrongArgumentsAgain() {
+        assertNotReached();
+    }
+
+    private static void wrongArgumentsAgain() {
+        System.out.println("wrongArgumentsAgain");
+    }
+
+    // Primitive argument types not supported {Z, B, C, S}.
+    private static CallSite bsmZBCS(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            boolean extraArg0,
+            byte extraArg1,
+            char extraArg2,
+            short extraArg3)
+            throws Throwable {
+        assertNotReached();
+        return null;
+    }
+
+    // Arguments are narrower than supported.
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmZBCS",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        boolean.class,
+                        byte.class,
+                        char.class,
+                        short.class
+                    }
+                ),
+        fieldOrMethodName = "narrowArguments",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(booleanValue = true),
+            @Constant(byteValue = Byte.MAX_VALUE),
+            @Constant(charValue = 'A'),
+            @Constant(shortValue = Short.MIN_VALUE)
+        }
+    )
+    private static void invokeNarrowArguments() {
+        assertNotReached();
+    }
+
+    private static void narrowArguments() {
+        assertNotReached();
+    }
+
+    private static CallSite bsmDJ(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            double extraArg0,
+            long extraArg1)
+            throws Throwable {
+        System.out.print("bsmDJ(..., ");
+        System.out.print(extraArg0);
+        System.out.print(", ");
+        System.out.print(extraArg1);
+        System.out.println(")");
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    // Arguments need widening to parameter types.
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmDJ",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        double.class,
+                        long.class
+                    }
+                ),
+        fieldOrMethodName = "wideningArguments",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(doubleValue = Double.MAX_VALUE),
+            @Constant(intValue = Integer.MAX_VALUE)
+        }
+    )
+    private static void invokeWideningArguments() {
+        assertNotReached();
+    }
+
+    private static void wideningArguments() {
+        System.out.println("wideningArguments");
+    }
+
+    private static CallSite bsmDoubleLong(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            Double extraArg0,
+            Long extraArg1)
+            throws Throwable {
+        System.out.print("bsmDoubleLong(..., ");
+        System.out.print(extraArg0);
+        System.out.print(", ");
+        System.out.print(extraArg1);
+        System.out.println(")");
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    // Arguments need boxing to parameter types
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmDoubleLong",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        Double.class,
+                        Long.class
+                    }
+                ),
+        fieldOrMethodName = "boxingArguments",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(doubleValue = Double.MAX_VALUE),
+            @Constant(longValue = Long.MAX_VALUE)
+        }
+    )
+    private static void invokeBoxingArguments() {
+        assertNotReached();
+    }
+
+    private static void boxingArguments() {
+        System.out.println("boxingArguments");
+    }
+
+    // Arguments need widening and boxing to parameter types
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmDoubleLong",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        Double.class,
+                        Long.class
+                    }
+                ),
+        fieldOrMethodName = "wideningBoxingArguments",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(floatValue = Float.MAX_VALUE),
+            @Constant(longValue = Integer.MAX_VALUE)
+        }
+    )
+    private static void invokeWideningBoxingArguments() {
+        assertNotReached();
+    }
+
+    private static void wideningBoxingArguments() {
+        System.out.println("wideningBoxingArguments");
+    }
+
+    static void bsmReturningVoid(MethodHandles.Lookup lookup, String name, MethodType type) {
+        System.out.println("bsm returning void value.");
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmReturningVoid",
+                    parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
+                    returnType = void.class
+                ),
+        fieldOrMethodName = "voidReturnType"
+    )
+    private static void invokeVoidReturnType() {
+        assertNotReached();
+    }
+
+    private static void voidReturnType() {
+        assertNotReached();
+    }
+
+    static Object bsmReturningObject(MethodHandles.Lookup lookup, String name, MethodType type) {
+        System.out.println("bsm returning Object value.");
+        return new Object();
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmReturningObject",
+                    parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
+                    returnType = Object.class
+                ),
+        fieldOrMethodName = "ObjectReturnType"
+    )
+    private static void invokeObjectReturnType() {
+        assertNotReached();
+    }
+
+    private static void objectReturnType() {
+        assertNotReached();
+    }
+
+    static Integer bsmReturningInteger(MethodHandles.Lookup lookup, String name, MethodType type) {
+        System.out.println("bsm returning Integer value.");
+        return Integer.valueOf(3);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmReturningInteger",
+                    parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
+                    returnType = Integer.class
+                ),
+        fieldOrMethodName = "integerReturnType"
+    )
+    private static void invokeIntegerReturnType() {
+        assertNotReached();
+    }
+
+    private static void integerReturnType() {
+        assertNotReached();
+    }
+
+    static class TestersConstantCallSite extends ConstantCallSite {
+        public TestersConstantCallSite(MethodHandle mh) {
+            super(mh);
+        }
+    }
+
+    static TestersConstantCallSite bsmReturningTestersConstantCallsite(
+            MethodHandles.Lookup lookup, String name, MethodType type) throws Throwable {
+        return new TestersConstantCallSite(lookup.findStatic(lookup.lookupClass(), name, type));
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestBadBootstrapArguments.class,
+                    name = "bsmReturningTestersConstantCallsite",
+                    parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
+                    returnType = TestersConstantCallSite.class
+                ),
+        fieldOrMethodName = "sayHello"
+    )
+    private static void invokeViaCustomCallSiteClass() {
+        assertNotReached();
+    }
+
+    private static void sayHello() {
+        System.out.println("Hello!");
+    }
+
+    static void test() {
+        System.out.println("TestBadBootstrapArguments");
+        invokeHappy();
+        try {
+            invokeWrongParameterTypes();
+            assertNotReached();
+        } catch (NoSuchMethodError expected) {
+            System.out.print("invokeWrongParameterTypes => ");
+            System.out.println(expected.getClass());
+        }
+        try {
+            invokeMissingParameterTypes();
+            assertNotReached();
+        } catch (NoSuchMethodError expected) {
+            System.out.print("invokeMissingParameterTypes => ");
+            System.out.println(expected.getClass());
+        }
+        try {
+            invokeExtraArguments();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            assertEquals(WrongMethodTypeException.class, expected.getCause().getClass());
+            System.out.print("invokeExtraArguments => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            invokeWrongArguments();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            assertEquals(ClassCastException.class, expected.getCause().getClass());
+            System.out.print("invokeWrongArguments => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            invokeWrongArguments();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            assertEquals(ClassCastException.class, expected.getCause().getClass());
+            System.out.print("invokeWrongArguments => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            invokeWrongArgumentsAgain();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            assertEquals(ClassCastException.class, expected.getCause().getClass());
+            System.out.print("invokeWrongArgumentsAgain => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            invokeNarrowArguments();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            assertEquals(ClassCastException.class, expected.getCause().getClass());
+            System.out.print("invokeNarrowArguments => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        invokeWideningArguments();
+        invokeBoxingArguments();
+        try {
+            invokeWideningBoxingArguments();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("invokeWideningBoxingArguments => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            invokeVoidReturnType();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("invokeVoidReturnType() => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            invokeObjectReturnType();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("invokeObjectReturnType() => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            invokeIntegerReturnType();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("invokeIntegerReturnType() => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        invokeViaCustomCallSiteClass();
+    }
+}
diff --git a/test/952-invoke-custom/src/TestDynamicBootstrapArguments.java b/test/952-invoke-custom/src/TestDynamicBootstrapArguments.java
new file mode 100644
index 0000000..782feca
--- /dev/null
+++ b/test/952-invoke-custom/src/TestDynamicBootstrapArguments.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import annotations.BootstrapMethod;
+import annotations.CalledByIndy;
+import annotations.Constant;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+class TestDynamicBootstrapArguments extends TestBase {
+    private static int bsmCalls = 0;
+
+    static CallSite bsm(
+            MethodHandles.Lookup lookup,
+            String name,
+            MethodType methodType,
+            String otherNameComponent,
+            long nameSuffix)
+            throws Throwable {
+        bsmCalls = bsmCalls + 1;
+        Class<?> definingClass = TestDynamicBootstrapArguments.class;
+        String methodName = name + otherNameComponent + nameSuffix;
+        MethodHandle mh = lookup.findStatic(definingClass, methodName, methodType);
+        System.out.println("bsm");
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestDynamicBootstrapArguments.class,
+                    name = "bsm",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        String.class,
+                        long.class
+                    }
+                ),
+        fieldOrMethodName = "target",
+        returnType = int.class,
+        parameterTypes = {int.class, String.class, double.class},
+        constantArgumentsForBootstrapMethod = {
+            @Constant(stringValue = "A"),
+            @Constant(longValue = 100000000l)
+        }
+    )
+    private static int testDynamic(int i, String s, Double d) {
+        assertNotReached();
+        return 0;
+    }
+
+    private static int targetA100000000(int i, String s, Double d) {
+        System.out.print(i);
+        System.out.print(", ");
+        System.out.print(s);
+        System.out.print(", ");
+        System.out.println(d);
+        return i;
+    }
+
+    static void testCallSites() {
+        assertEquals(0, testDynamic(0, "One", Math.PI));
+        assertEquals(1, testDynamic(1, "Two", Math.E));
+        assertEquals(2, testDynamic(2, "Three", 0.0));
+    }
+
+    static void test() {
+        System.out.println("TestDynamicArguments");
+        testCallSites();
+        assertEquals(3, bsmCalls);
+        testCallSites();
+        assertEquals(3, bsmCalls);
+    }
+}
diff --git a/test/952-invoke-custom/src/TestInvocationKinds.java b/test/952-invoke-custom/src/TestInvocationKinds.java
index 7b88c18..f743bef 100644
--- a/test/952-invoke-custom/src/TestInvocationKinds.java
+++ b/test/952-invoke-custom/src/TestInvocationKinds.java
@@ -173,6 +173,7 @@
 
     static class Widget {
         int value;
+
         public Widget(int value) {}
     }
 
diff --git a/test/952-invoke-custom/src/TestVariableArityLinkerMethod.java b/test/952-invoke-custom/src/TestVariableArityLinkerMethod.java
new file mode 100644
index 0000000..597273c
--- /dev/null
+++ b/test/952-invoke-custom/src/TestVariableArityLinkerMethod.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import annotations.BootstrapMethod;
+import annotations.CalledByIndy;
+import annotations.Constant;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Arrays;
+
+public class TestVariableArityLinkerMethod extends TestBase {
+    private static void printBsmArgs(String method, Object... args) {
+        System.out.print(method);
+        System.out.print("(");
+        for (int i = 0; i < args.length; ++i) {
+            if (i != 0) {
+                System.out.print(", ");
+            }
+            if (args[i] != null && args[i].getClass().isArray()) {
+                Object array = args[i];
+                if (array.getClass() == int[].class) {
+                    System.out.print(Arrays.toString((int[]) array));
+                } else if (array.getClass() == long[].class) {
+                    System.out.print(Arrays.toString((long[]) array));
+                } else if (array.getClass() == float[].class) {
+                    System.out.print(Arrays.toString((float[]) array));
+                } else if (array.getClass() == double[].class) {
+                    System.out.print(Arrays.toString((double[]) array));
+                } else {
+                    System.out.print(Arrays.toString((Object[]) array));
+                }
+            } else {
+                System.out.print(args[i]);
+            }
+        }
+        System.out.println(");");
+    }
+
+    private static CallSite bsmWithStringArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            String... arityArgs)
+            throws Throwable {
+        printBsmArgs("bsmWithStringArray", lookup, methodName, methodType, arityArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodA",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(stringValue = "Aachen"),
+            @Constant(stringValue = "Aalborg"),
+            @Constant(stringValue = "Aalto")
+        }
+    )
+    private static void methodA() {
+        System.out.println("methodA");
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodB",
+        constantArgumentsForBootstrapMethod = {@Constant(stringValue = "barium")}
+    )
+    private static void methodB() {
+        System.out.println("methodB");
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodC"
+    )
+    private static void methodC() {
+        System.out.println("methodC");
+    }
+
+    private static CallSite bsmWithIntAndStringArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            int extraInt,
+            String... extraArityArgs)
+            throws Throwable {
+        printBsmArgs(
+                "bsmWithIntAndStringArray",
+                lookup,
+                methodName,
+                methodType,
+                extraInt,
+                extraArityArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithIntAndStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodD",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(intValue = 101),
+            @Constant(stringValue = "zoo"),
+            @Constant(stringValue = "zoogene"),
+            @Constant(stringValue = "zoogenic")
+        }
+    )
+    private static void methodD() {
+        System.out.println("methodD");
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithIntAndStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodE",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(intValue = 102),
+            @Constant(stringValue = "zonic")
+        }
+    )
+    private static void methodE() {
+        System.out.println("methodE");
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithIntAndStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodF",
+        constantArgumentsForBootstrapMethod = {@Constant(intValue = 103)}
+    )
+    private static void methodF() {
+        System.out.println("methodF");
+    }
+
+    private static CallSite bsmWithLongAndIntArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            long extraArg,
+            int... arityArgs)
+            throws Throwable {
+        printBsmArgs("bsmWithLongAndIntArray", lookup, methodName, methodType, extraArg, arityArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithLongAndIntArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        long.class,
+                        int[].class
+                    }
+                ),
+        fieldOrMethodName = "methodG",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(longValue = 0x123456789abcdefl),
+            @Constant(intValue = +1),
+            @Constant(intValue = -1),
+            @Constant(intValue = +2),
+            @Constant(intValue = -2)
+        }
+    )
+    private static void methodG() {
+        System.out.println("methodG");
+    }
+
+    private static CallSite bsmWithFloatAndLongArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            float extraArg,
+            long... arityArgs)
+            throws Throwable {
+        printBsmArgs(
+                "bsmWithFloatAndLongArray", lookup, methodName, methodType, extraArg, arityArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithFloatAndLongArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        float.class,
+                        long[].class
+                    }
+                ),
+        fieldOrMethodName = "methodH",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(floatValue = (float) -Math.E),
+            @Constant(longValue = 999999999999l),
+            @Constant(longValue = -8888888888888l)
+        }
+    )
+    private static void methodH() {
+        System.out.println("methodH");
+    }
+
+    private static CallSite bsmWithClassAndFloatArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            Class<?> extraArg,
+            float... arityArgs)
+            throws Throwable {
+        printBsmArgs(
+                "bsmWithClassAndFloatArray", lookup, methodName, methodType, extraArg, arityArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithClassAndFloatArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        Class.class,
+                        float[].class
+                    }
+                ),
+        fieldOrMethodName = "methodI",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(classValue = Throwable.class),
+            @Constant(floatValue = Float.MAX_VALUE),
+            @Constant(floatValue = Float.MIN_VALUE),
+            @Constant(floatValue = (float) Math.PI),
+            @Constant(floatValue = (float) -Math.PI)
+        }
+    )
+    private static void methodI() {
+        System.out.println("methodI");
+    }
+
+    private static CallSite bsmWithDoubleArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            double... arityArgs)
+            throws Throwable {
+        printBsmArgs("bsmWithDoubleArray", lookup, methodName, methodType, arityArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithDoubleArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        double[].class
+                    }
+                ),
+        fieldOrMethodName = "methodJ",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(doubleValue = Double.MAX_VALUE),
+            @Constant(doubleValue = Double.MIN_VALUE),
+            @Constant(doubleValue = Math.E),
+            @Constant(doubleValue = -Math.PI)
+        }
+    )
+    private static void methodJ() {
+        System.out.println("methodJ");
+    }
+
+    private static CallSite bsmWithClassArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            Class... arityArgs)
+            throws Throwable {
+        printBsmArgs("bsmWithClassArray", lookup, methodName, methodType, arityArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithClassArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        Class[].class
+                    }
+                ),
+        fieldOrMethodName = "methodK",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(classValue = Integer.class),
+            @Constant(classValue = MethodHandles.class),
+            @Constant(classValue = Arrays.class)
+        }
+    )
+    private static void methodK() {
+        System.out.println("methodK");
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithIntAndStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodO",
+        constantArgumentsForBootstrapMethod = {@Constant(intValue = 103), @Constant(intValue = 104)}
+    )
+    private static void methodO() {
+        // Arguments are not compatible
+        assertNotReached();
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithIntAndStringArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        int.class,
+                        String[].class
+                    }
+                ),
+        fieldOrMethodName = "methodP",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(intValue = 103),
+            @Constant(stringValue = "A"),
+            @Constant(stringValue = "B"),
+            @Constant(intValue = 42)
+        }
+    )
+    private static void methodP() {
+        // Arguments are not compatible - specifically, the third
+        // component of potential collector array is an integer
+        // argument (42).
+        assertNotReached();
+    }
+
+    private static CallSite bsmWithWiderArray(
+            MethodHandles.Lookup lookup, String methodName, MethodType methodType, long[] extraArgs)
+            throws Throwable {
+        printBsmArgs("bsmWithWiderArray", lookup, methodName, methodType, extraArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithWiderArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        long[].class
+                    }
+                ),
+        fieldOrMethodName = "methodQ",
+        constantArgumentsForBootstrapMethod = {@Constant(intValue = 103), @Constant(intValue = 42)}
+    )
+    private static void methodQ() {
+        assertNotReached();
+    }
+
+    private static CallSite bsmWithBoxedArray(
+            MethodHandles.Lookup lookup,
+            String methodName,
+            MethodType methodType,
+            Integer[] extraArgs)
+            throws Throwable {
+        printBsmArgs("bsmWithBoxedArray", lookup, methodName, methodType, extraArgs);
+        MethodHandle mh = lookup.findStatic(lookup.lookupClass(), methodName, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestVariableArityLinkerMethod.class,
+                    name = "bsmWithBoxedArray",
+                    parameterTypes = {
+                        MethodHandles.Lookup.class,
+                        String.class,
+                        MethodType.class,
+                        Integer[].class
+                    }
+                ),
+        fieldOrMethodName = "methodR",
+        constantArgumentsForBootstrapMethod = {
+            @Constant(intValue = 1030),
+            @Constant(intValue = 420)
+        }
+    )
+    private static void methodR() {
+        assertNotReached();
+    }
+
+    static void test() {
+        // Happy cases
+        for (int i = 0; i < 2; ++i) {
+            methodA();
+            methodB();
+            methodC();
+        }
+        for (int i = 0; i < 2; ++i) {
+            methodD();
+            methodE();
+            methodF();
+        }
+        methodG();
+        methodH();
+        methodI();
+        methodJ();
+        methodK();
+
+        // Broken cases
+        try {
+            // bsm has incompatible static methods. Collector
+            // component type is String, the corresponding static
+            // arguments are int values.
+            methodO();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("methodO => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            // bsm has a trailing String array for the collector array.
+            // There is an int value amongst the String values.
+            methodP();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("methodP => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            // bsm has as trailing long[] element for the collector array.
+            // The corresponding static bsm arguments are of type int.
+            methodQ();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("methodQ => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+        try {
+            // bsm has as trailing Integer[] element for the collector array.
+            // The corresponding static bsm arguments are of type int.
+            methodR();
+            assertNotReached();
+        } catch (BootstrapMethodError expected) {
+            System.out.print("methodR => ");
+            System.out.print(expected.getClass());
+            System.out.print(" => ");
+            System.out.println(expected.getCause().getClass());
+        }
+    }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 5fb7819..b2f579d 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -934,7 +934,6 @@
           "946-obsolete-throw",
           "948-change-annotations",
           "950-redefine-intrinsic",
-          "952-invoke-custom",
           "954-invoke-polymorphic-verifier",
           "955-methodhandles-smali",
           "956-methodhandles",