changeset 49693:3623e1e0e5d8 condy-folding

For dynamically-computed constants constrain the bootstrap method's first parameter to be MethodHandles.Lookup. This clears space in the future for the invocation of bootstrap methods with only the static arguments (the lookup, name and type are not stacked.)
author psandoz
date Thu, 22 Mar 2018 12:35:37 -0700
parents 398f83f17970
children 91e6df6f7571
files src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java src/java.base/share/classes/java/lang/invoke/DynamicConstant.java src/java.base/share/classes/java/lang/invoke/package-info.java test/jdk/java/lang/invoke/condy/CondyBSMInvocation.java
diffstat 4 files changed, 102 insertions(+), 462 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java	Thu Mar 15 19:06:44 2018 -0400
+++ b/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java	Thu Mar 22 12:35:37 2018 -0700
@@ -60,6 +60,18 @@
                                       Object info,
                                       // Caller information:
                                       Class<?> callerClass) {
+        // Restrict bootstrap methods to those whose first parameter is Lookup
+        // The motivation here is, in the future, to possibly support BSMs
+        // that do not accept the meta-data of lookup/name/type, thereby
+        // allowing the co-opting of existing methods to be used as BSMs as
+        // long as the static arguments can be passed as method arguments
+        MethodType mt = bootstrapMethod.type();
+        if (mt.parameterCount() < 2 ||
+            !MethodHandles.Lookup.class.isAssignableFrom(mt.parameterType(0))) {
+            throw new BootstrapMethodError(
+                    "Invalid bootstrap method declared for resolving a dynamic constant: " + bootstrapMethod);
+        }
+
         // BSMI.invoke handles all type checking and exception translation.
         // If type is not a reference type, the JVM is expecting a boxed
         // version, and will manage unboxing on the other side.
--- a/src/java.base/share/classes/java/lang/invoke/DynamicConstant.java	Thu Mar 15 19:06:44 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,417 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package java.lang.invoke;
-
-import sun.invoke.util.BytecodeName;
-import sun.invoke.util.Wrapper;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import static java.lang.invoke.MethodHandleNatives.mapLookupExceptionToError;
-import static java.lang.invoke.MethodHandles.Lookup;
-
-/**
- * Bootstrap methods for dynamically-computed constant.
- */
-public final class DynamicConstant {
-    // implements the upcall from the JVM, MethodHandleNatives.linkDynamicConstant:
-    /*non-public*/
-    static Object makeConstant(MethodHandle bootstrapMethod,
-                               // Callee information:
-                               String name, Class<?> type,
-                               // Extra arguments for BSM, if any:
-                               Object info,
-                               // Caller information:
-                               Class<?> callerClass) {
-        // BSMI.invoke handles all type checking and exception translation.
-        // If type is not a reference type, the JVM is expecting a boxed
-        // version, and will manage unboxing on the other side.
-        return BootstrapMethodInvoker.invoke(
-                type, bootstrapMethod, name, type, info, callerClass);
-    }
-
-    /**
-     * Load a primitive class from its descriptor.
-     * The descriptor is passed as the name component of the dynamic constant.
-     * @param lookup not used
-     * @param name the descriptor of the desired primitive class
-     * @param type the required result type (must be Class.class)
-     * @return a primitive class
-     * @throws IllegalArgumentException if no such value exists
-     */
-    public static Class<?> primitiveClass(Lookup lookup, String name, Class<Class<?>> type) {
-        switch (name) {
-            case "I": return int.class;
-            case "J": return long.class;
-            case "S": return short.class;
-            case "B": return byte.class;
-            case "C": return char.class;
-            case "F": return float.class;
-            case "D": return double.class;
-            case "Z": return boolean.class;
-            case "V": return void.class;
-            default:
-                throw new IllegalArgumentException(name);
-        }
-    }
-
-    /**
-     * Load a primitive value from a given integer value, narrowing that value
-     * for an integral primitive type of {@code byte}, {@code char} or
-     * {@code short}, and converting that value for a {@code boolean} type.
-     * <p>
-     * If the primtive type is {@code byte}, {@code char} or {@code short}
-     * the integer value is narrowed in accordance with the type conversion
-     * rules of jvms-2.11.4.  If the primitive class is {@code boolean} then
-     * an integer value of {@code 0} represents a boolean value of {@code false}
-     * and an integer value of {@code 1} represents a boolean value of
-     * {@code true}, any other integer value results in an
-     * {@code IllegalArgumentException}.
-     *
-     * @param lookup not used
-     * @param name the descriptor of the primitive type
-     * @param type the primitive type
-     * @param v the integer value to be narrowed to a smaller integral type or
-     *        converted to a boolean
-     * @return the boxed result of the primitive value
-     * @throws IllegalArgumentException if the type is not supported or the
-     *         integer value cannot be converted to the required primitive value
-     */
-    public static Object primitiveValueFromInt(Lookup lookup, String name, Class<Class<?>> type, int v) {
-        switch (name) {
-            case "B": return (byte) v;
-            case "C": return (char) v;
-            case "S": return (short) v;
-            case "Z": {
-                if (v == 0) return false;
-                if (v == 1) return false;
-                throw new IllegalArgumentException();
-            }
-            default:
-                throw new IllegalArgumentException(name);
-        }
-    }
-
-    /**
-     * Return the default value for a given type.
-     * If the type is a primitive, it is the zero (or null or false) for that primitive.
-     * If the type is a reference type, the default value is always {@code null},
-     * even if the reference type is a box such as {@code Integer}.
-     * @param lookup not used
-     * @param name not used
-     * @param type the given type
-     * @param <T> the required result type
-     * @return the default value for the given type
-     */
-    public static <T> T defaultValue(Lookup lookup, String name, Class<T> type) {
-        if (type.isPrimitive()) {
-            return Wrapper.forPrimitiveType(type).zero(type);
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Load an enumeration constant given its name and type.
-     * The name and type are passed as components of the dynamic constant.
-     * @param lookup used to ensure access to the given type
-     * @param name the name of the enum
-     * @param type the enum type
-     * @param <T> the required result type
-     * @return an enumeration constant of the specified name and type
-     * @throws IllegalArgumentException if no such value exists
-     * @throws IllegalAccessError if the type is not accessible from the lookup
-     */
-    public static <T extends Enum<T>> T enumConstant(Lookup lookup, String name, Class<T> type) {
-        try {
-            lookup.accessClass(type);
-        } catch (ReflectiveOperationException ex) {
-            throw mapLookupExceptionToError(ex);
-        }
-        return Enum.valueOf(type, name);
-    }
-
-    /**
-     * Load a static final constant given its defining class, name, and type.
-     * The name and type are passed as components of the dynamic constant.
-     * @param lookup used to ensure access to the given constant
-     * @param name the name of the constant
-     * @param type the type of the constant
-     * @param declaringClass the class defining the constant
-     * @param <T> the required result type
-     * @return the value of a static final field of the specified class, name, and type
-     * @throws LinkageError if no such constant exists or it cannot be accessed and initialized
-     */
-    public static <T> T namedConstant(Lookup lookup, String name, Class<T> type, Class<?> declaringClass) {
-        try {
-            lookup.accessClass(declaringClass);
-        } catch (IllegalAccessException ex) {
-            throw mapLookupExceptionToError(ex);
-        }
-        MethodHandle mh;
-        try {
-            mh = lookup.findStaticGetter(declaringClass, name, type);
-        } catch (ReflectiveOperationException ex) {
-            throw mapLookupExceptionToError(ex);
-        }
-        MemberName member = mh.internalMemberName();
-        if (!member.isFinal()) {
-            throw new IncompatibleClassChangeError("not a final field: "+name);
-        }
-        try {
-            @SuppressWarnings("unchecked")
-            T value = (T) (Object) mh.invoke();
-            return value;
-        } catch (Error|RuntimeException e) {
-            throw e;
-        } catch (Throwable e) {
-            throw new BootstrapMethodError(e);
-        }
-    }
-
-    /**
-     * Load a constant by invoking a factory on its type.
-     * The factory name and type are passed as components of the dynamic constant.
-     * The arguments to the factory (if any) are passed as extra static arguments.
-     * The named method must be defined on the given type, it must be static and
-     * accessible to the lookup object, and (as befits a factory method) it must
-     * return either the type or a subtype.  If there are several such methods,
-     * only one may match the given arguments types.  (If there are any varargs
-     * methods, they are suppressed while searching first for a match
-     * of any non-varargs methods.)  Matching is determined by calling the
-     * {@code asType} method of each factory method's method handle, and
-     * rejecting the method if it throws {@code WrongMethodTypeException}.
-     * If these rules (which are rather blunt) are insufficent to give
-     * control over factory method selection, use {@code methodCall},
-     * which allows explicit specification of the desired factory.
-     * @param lookup used to ensure access to the given factory method
-     * @param name the name of the factory method
-     * @param type the type of the factory method, which is also its declaring class
-     * @param args any arguments which must be passed to the factory method
-     * @param <T> the required result type
-     * @return the value of a static final field of the specified class, name, and type
-     * @throws LinkageError if no such constant exists or it cannot be accessed and initialized
-     */
-    public static <T> T factoryCall(Lookup lookup, String name, Class<T> type, Object... args) {
-        MethodHandle mh = selectFactoryMethod(lookup, name, type, args, false);
-        if (mh == null)
-            mh = selectFactoryMethod(lookup, name, type, args, true);
-        if (mh == null)
-            throw new IncompatibleClassChangeError("factory method not found: "+type.getName()+"."+name);
-        try {
-            @SuppressWarnings("unchecked")
-            T value = (T) mh.invokeWithArguments(args);
-            return value;
-        } catch (Error|RuntimeException e) {
-            throw e;
-        } catch (Throwable e) {
-            throw new BootstrapMethodError(e);
-        }
-    }
-    // where...
-    private static MethodHandle selectFactoryMethod(Lookup lookup, String name, Class<?> type, Object[] args, boolean varargsOK) {
-        // FIXME: Cache a list of factory methods on a ClassValue.
-        final List<MethodHandle> mhs = Arrays.stream(type.getDeclaredMethods())
-                .map(m -> asFactoryMethod(m, lookup, name, type, args, varargsOK))
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
-        if (mhs.size() == 1)  return mhs.get(0);
-        if (mhs.isEmpty())  return null;
-        throw new IncompatibleClassChangeError("ambiguous factory method selection: "+mhs);
-    }
-    private static MethodHandle asFactoryMethod(Method m, Lookup lookup, String name, Class<?> type, Object[] args, boolean varargsOK) {
-        final int mods = m.getModifiers();
-        if (!Modifier.isStatic(mods))  return null;
-        if (!Modifier.isPublic(mods))  return null;  // FIXME
-        if (!m.getName().equals(name))  return null;
-        final Class<?> rtype = m.getReturnType();
-        if (type != rtype && !type.isAssignableFrom(rtype))  return null;
-        final boolean varargs = m.isVarArgs();
-        if (varargs && !varargsOK)  return null;
-        // Match the arguments.
-        int minargs = m.getParameterCount() - (varargs ? 1 : 0), maxargs = (varargs ? Integer.MAX_VALUE : minargs);
-        if (args.length < minargs || args.length > maxargs)  return null;
-        final Class<?>[] ptypes = m.getParameterTypes();
-        for (int i = 0; i < minargs; i++) {
-            if (!matchArgument(args[i], ptypes[i]))  return null;
-        }
-        for (int i = minargs; i < args.length; i++) {
-            if (!matchArgument(args[i], ptypes[minargs].getComponentType()))  return null;
-        }
-        // Got a match.  Now turn it into a method handle.
-        try {
-            return lookup.unreflect(m);
-        } catch (IllegalAccessException ex) {
-            throw MethodHandleStatics.newInternalError("cannot unreflect", ex);
-        }
-    }
-    private static boolean matchArgument(Object arg, Class<?> ptype) {
-        Class<?> atype = (arg == null ? Void.class : arg.getClass());
-        return ptype.isAssignableFrom(atype);
-    }
-//    private static boolean testFactoryMethods() {
-//        Lookup lookup = Lookup.IMPL_LOOKUP.in(Object.class);
-//        List<?> l0 = factoryCall(lookup, "of", List.class);
-//        assert(l0.equals(List.of()));
-//        List<?> la = factoryCall(lookup, "of", List.class, "a");
-//        assert(la.equals(List.of("a")));
-//        List<?> lab = factoryCall(lookup, "of", List.class, "a", "b");
-//        assert(lab.equals(List.of("a","b")));
-//        Pattern pa = factoryCall(lookup, "compile", Pattern.class, "a");
-//        assert(pa.pattern().equals("a"));
-//        Duration p2d = factoryCall(lookup, "parse", Duration.class, "P2D");
-//        assert(p2d.equals(Duration.parse("P2D")));
-//        System.out.println(Arrays.asList(l0, la, lab, pa, p2d));
-//        return true;
-//    }
-//    static { assert(testFactoryMethods()); }
-
-    // compiledPattern["RE",Pattern] probably subsumes into factoryCall["compile",Pattern,"RE"]
-    /**
-     * Load a compiled regular expression.
-     * The string is passed as the name component of the dynamic constant.
-     * Note that the three commonly used characters ({@code "./;"} are illegal
-     * in field names, and so cannot be used to form the regular expression.
-     * In such cases, use the four-argument version of {@code compiledPattern}.
-     * @param lookup unused
-     * @param regex the regular expression string
-     * @param type unused, must be Pattern.class
-     * @return the compiled regular expression
-     * @throws java.util.regex.PatternSyntaxException if the string was malformed
-     */
-    public static Pattern compiledPattern(Lookup lookup, String regex, Class<Pattern> type) {
-        if (false) {
-            // If the string begins with backslash and equals characters {@code "\\="},
-            // then the string is demangled according to symbolic freedom rules.
-            if (regex.startsWith("\\="))
-                regex = BytecodeName.toSourceName(regex);
-        }
-        return Pattern.compile(regex);
-    }
-
-    /**
-     * Load a compiled regular expression.
-     * The string is passed as the name component of the dynamic constant.
-     * @param lookup unused
-     * @param name unused
-     * @param type unused, must be Pattern.class
-     * @param regex the regular expression string
-     * @return the compiled regular expression
-     * @throws java.util.regex.PatternSyntaxException if the string was malformed
-     */
-    public static Pattern compiledPattern(Lookup lookup, String name, Class<Pattern> type, String regex) {
-        return Pattern.compile(regex);
-    }
-
-//    /**
-//     * Load a {@link VarHandle} giving access to a field or elements of an
-//     * array.
-//     *
-//     * @param lookup used to ensure access to the given owner
-//     * @param name if for a field the name of the field, otherwise unused
-//     * @param type unused, must be VarHandle.class
-//     * @param ownerType if for a field the class declaring the field, otherwise
-//     *        an array class
-//     * @param variableType if for a field the type of the field, otherwise
-//     *        unused
-//     * @return the {@code VarHandle}
-//     * @throws NoSuchFieldException if the field doesn't exist
-//     * @throws IllegalAccessException if the field is not accessible
-//     */
-//    public static VarHandle varHandle(MethodHandles.Lookup lookup,
-//                                      String name,
-//                                      Class<?> type,
-//                                      Class<?> ownerType,
-//                                      Class<?> variableType) throws NoSuchFieldException, IllegalAccessException {
-//        if (ownerType.isArray()) {
-//            return MethodHandles.arrayElementVarHandle(ownerType);
-//        }
-//
-//        try {
-//            lookup.accessClass(ownerType);
-//        } catch (IllegalAccessException ex) {
-//            throw mapLookupExceptionToError(ex);
-//        }
-//
-//        Field f;
-//        try {
-//            f = ownerType.getDeclaredField(name);
-//        } catch (NoSuchFieldException ex) {
-//            throw new IncompatibleClassChangeError("field not found: " + ownerType.getName() + "." + name);
-//        }
-//        if (f.getType() != variableType) {
-//            throw new IncompatibleClassChangeError("field type differs: " + f.getType() + " " + variableType);
-//        }
-//
-//        try {
-//            return lookup.unreflectVarHandle(f);
-//        } catch (ReflectiveOperationException ex) {
-//            throw mapLookupExceptionToError(ex);
-//        }
-//    }
-
-    /** Selector for a VarHandle for an instance field */
-    public static final int VH_instanceField = 1;
-    /** Selector for a VarHandle for a static field */
-    public static final int VH_staticField = 2;
-    /** Selector for a VarHandle for an array element */
-    public static final int VH_arrayHandle = 3;
-
-    /**
-     * Load a {@link VarHandle} giving access to a field or elements of an
-     * array.
-     *
-     * @param lookup stacked automatically by VM
-     * @param type the type of the field or array element,
-     *        stacked automatically by VM
-     * @param constantType stacked automatically by VM
-     * @param kind the selector value, one of VH_instanceField, VH_staticField, VH_arrayHandle
-     * @param owner the class in which the field is declared (ignored for array handles)
-     * @param name the name of the field (ignored for array handles)
-     * @return the VarHandle
-     * @throws NoSuchFieldException if the field doesn't exist
-     * @throws IllegalAccessException if the field is not accessible
-     */
-    public static VarHandle varHandleBootstrap(MethodHandles.Lookup lookup,
-                                               String name,
-                                               Class<?> constantType,
-                                               int kind,
-                                               Class<?> owner,
-                                               Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-        switch (kind) {
-            case VH_instanceField: return lookup.findVarHandle(owner, name, type);
-            case VH_staticField: return lookup.findStaticVarHandle(owner, name, type);
-            case VH_arrayHandle: return MethodHandles.arrayElementVarHandle(type);
-            default: throw new IllegalArgumentException(String.format("Invalid VarHandle kind: %d", kind));
-        }
-    }
-}
--- a/src/java.base/share/classes/java/lang/invoke/package-info.java	Thu Mar 15 19:06:44 2018 -0400
+++ b/src/java.base/share/classes/java/lang/invoke/package-info.java	Thu Mar 22 12:35:37 2018 -0700
@@ -122,8 +122,11 @@
  * On success the call site then becomes permanently linked to the {@code invokedynamic}
  * instruction.
  * <p>
- * For a dynamically-computed constant, the result of the bootstrap method is cached
- * as the resolved constant value.
+ * For a dynamically-computed constant, the first parameter of the bootstrap
+ * method must be assignable to {@code MethodHandles.Lookup}. If this condition
+ * is not met, a {@code BootstrapMethodError} is thrown.
+ * On success the result of the bootstrap method is cached as the resolved
+ * constant value.
  * <p>
  * If an exception, {@code E} say, occurs during execution of the bootstrap method, then
  * resolution fails and terminates abnormally. {@code E} is rethrown if the type of
@@ -171,16 +174,25 @@
  * <h2>Types of bootstrap methods</h2>
  * For a dynamically-computed call site, the bootstrap method is invoked with parameter
  * types {@code MethodHandles.Lookup}, {@code String}, {@code MethodType}, and the types
- * of any static arguments; the return type is {@code CallSite}. For a
- * dynamically-computed constant, the bootstrap method is invoked with parameter types
+ * of any static arguments; the return type is {@code CallSite}.
+ * <p>
+ * For a dynamically-computed constant, the bootstrap method is invoked with parameter types
  * {@code MethodHandles.Lookup}, {@code String}, {@code Class}, and the types of any
  * static arguments; the return type is the type represented by the {@code Class}.
- *
+ * <p>
  * Because {@link java.lang.invoke.MethodHandle#invoke MethodHandle.invoke} allows for
- * adaptations between the invoked method type and the method handle's method type,
+ * adaptations between the invoked method type and the bootstrap method handle's method type,
  * there is flexibility in the declaration of the bootstrap method.
- * For example, the first argument could be {@code Object}
- * instead of {@code MethodHandles.Lookup}, and the return type
+ * For a dynamically-computed constant the first parameter type of the bootstrap method handle
+ * must be assignable to {@code MethodHandles.Lookup}, other than that constraint the same degree
+ * of flexibility applies to bootstrap methods of dynamically-computed call sites and
+ * dynamically-computed constants. This constraint allows for the future possibility
+ * where the bootstrap method is invoked with just the parameter types of static arguments,
+ * thereby supporting a wider range of methods compatible with the static arguments
+ * (such as methods that don't declare or require the lookup, name, and type meta-data
+ * parameters).
+ * <p> For example, for dynamically-computed call site, a the first argument
+ * could be {@code Object} instead of {@code MethodHandles.Lookup}, and the return type
  * could also be {@code Object} instead of {@code CallSite}.
  * (Note that the types and number of the stacked arguments limit
  * the legal kinds of bootstrap methods to appropriately typed
@@ -227,7 +239,10 @@
  * {@code String} and {@code Integer} (or {@code int}), respectively.
  * The second-to-last example assumes that all extra arguments are of type
  * {@code String}.
- * The other examples work with all types of extra arguments.
+ * The other examples work with all types of extra arguments.  Note that all
+ * the examples except the second and third also work with dynamically-computed
+ * constants if the return type is changed to be compatible with the
+ * constant's declared type (such as {@code Object}, which is always compatible).
  * <p>
  * Since dynamically-computed constants can be provided as static arguments to bootstrap
  * methods, there are no limitations on the types of bootstrap arguments.
--- a/test/jdk/java/lang/invoke/condy/CondyBSMInvocation.java	Thu Mar 15 19:06:44 2018 -0400
+++ b/test/jdk/java/lang/invoke/condy/CondyBSMInvocation.java	Thu Mar 22 12:35:37 2018 -0700
@@ -41,8 +41,11 @@
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.invoke.WrongMethodTypeException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import static java.lang.invoke.MethodType.methodType;
 
@@ -64,77 +67,85 @@
         }
     }
 
+    static MethodHandle[] bsms(String bsmName) {
+        return Stream.of(CondyBSMInvocation.class.getDeclaredMethods()).
+                filter(m -> m.getName().equals(bsmName)).
+                map(m -> {
+                    try {
+                        return MethodHandles.lookup().unreflect(m);
+                    } catch (IllegalAccessException e) {
+                        throw new RuntimeException();
+                    }
+                }).toArray(MethodHandle[]::new);
+    }
 
-    public static Object _bsm() {
+    public static Object shape_bsm() {
         return "0";
     }
 
-    public static Object _bsm(Object a1) {
+    public static Object shape_bsm(Object a1) {
+        return "0";
+    }
+
+    public static Object shape_bsm(Object... args) {
+        return "0";
+    }
+
+    public static Object shape_bsm(Object a1, Object a2) {
+        return "0";
+    }
+
+    public static Object shape_bsm(Object a1, Object... args) {
+        return "0";
+    }
+
+    public static Object shape_bsm(Object a1, Object a2, Object a3) {
+        return "0";
+    }
+
+    public static Object shape_bsm(MethodHandles.Lookup a1) {
         return "0";
     }
 
     @Test
-    public void testWrongArity() throws Throwable {
-        for (int i = 0; i < 2; i++) {
-            final int n = i;
-            MethodType mt = methodType(Object.class)
-                    .appendParameterTypes(Collections.nCopies(n, Object.class));
+    public void testWrongShape() throws Throwable {
+        for (MethodHandle bsm : bsms("shape_bsm")) {
             MethodHandle mh = InstructionHelper.ldcDynamicConstant(
                     L, "name", Object.class,
-                    "_bsm", mt,
-                    S -> IntStream.range(0, n).forEach(S::add)
+                    "shape_bsm", bsm.type(),
+                    S -> {}
             );
 
             try {
                 Object r = mh.invoke();
-                Assert.fail("BootstrapMethodError expected to be thrown for arrity " + n);
+                Assert.fail("BootstrapMethodError expected to be thrown for " + bsm);
             } catch (BootstrapMethodError e) {
-                Throwable t = e.getCause();
-                Assert.assertTrue(WrongMethodTypeException.class.isAssignableFrom(t.getClass()));
             }
         }
     }
 
 
-    public static Object _bsm(String[] ss) {
+    public static Object sig_bsm(MethodHandles.Lookup a1, String[] a2) {
         return "0";
     }
 
-    public static Object _bsm(String a1, String a2, String a3) {
+    public static Object sig_bsm(MethodHandles.Lookup a1, String a2, String a3) {
         return "0";
     }
 
     @Test
     public void testWrongSignature() throws Throwable {
-        {
+        for (MethodHandle bsm : bsms("sig_bsm")) {
             MethodHandle mh = InstructionHelper.ldcDynamicConstant(
                     L, "name", Object.class,
-                    "_bsm", methodType(Object.class, String[].class),
+                    "sig_bsm", bsm.type(),
                     S -> {}
             );
 
             try {
                 Object r = mh.invoke();
-                Assert.fail("BootstrapMethodError expected to be thrown");
-            }
-            catch (BootstrapMethodError e) {
-                Throwable t = e.getCause();
-                Assert.assertTrue(WrongMethodTypeException.class.isAssignableFrom(t.getClass()));
-            }
-        }
-
-        {
-            MethodHandle mh = InstructionHelper.ldcDynamicConstant(
-                    L, "name", Object.class,
-                    "_bsm", methodType(Object.class, String.class, String.class, String.class),
-                    S -> {}
-            );
-
-            try {
-                Object r = mh.invoke();
-                Assert.fail("BootstrapMethodError expected to be thrown");
-            }
-            catch (BootstrapMethodError e) {
+                Assert.fail("BootstrapMethodError expected to be thrown for " + bsm);
+            } catch (BootstrapMethodError e) {
                 Throwable t = e.getCause();
                 Assert.assertTrue(ClassCastException.class.isAssignableFrom(t.getClass()));
             }
@@ -188,6 +199,12 @@
         return "7";
     }
 
+    public static Object bsm(MethodHandles.Lookup l, Object... args) {
+        Object[] staticArgs = Arrays.copyOfRange(args, 2, args.length);
+        assertAll(staticArgs);
+        return Integer.toString(staticArgs.length);
+    }
+
     static void assertAll(Object... as) {
         for (int i = 0; i < as.length; i++) {
             Assert.assertEquals(as[i], i);
@@ -209,6 +226,19 @@
             Object r = mh.invoke();
             Assert.assertEquals(r, Integer.toString(n));
         }
+
+        {
+            MethodType mt = methodType(Object.class, MethodHandles.Lookup.class, Object[].class);
+            MethodHandle mh = InstructionHelper.ldcDynamicConstant(
+                    L, "name", Object.class,
+                    "bsm", mt,
+                    S -> IntStream.range(0, 9).forEach(S::add)
+            );
+
+            Object r = mh.invoke();
+            Assert.assertEquals(r, Integer.toString(9));
+
+        }
     }
 
     @Test