1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package java.lang.invoke;
  26 
  27 import sun.invoke.util.Wrapper;
  28 
  29 import java.lang.invoke.AbstractConstantGroup.BSCIWithCache;
  30 import java.util.Arrays;
  31 
  32 import static java.lang.invoke.BootstrapCallInfo.makeBootstrapCallInfo;
  33 import static java.lang.invoke.ConstantGroup.makeConstantGroup;
  34 import static java.lang.invoke.MethodHandleNatives.*;
  35 import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE;
  36 import static java.lang.invoke.MethodHandles.Lookup;
  37 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
  38 
  39 final class BootstrapMethodInvoker {
  40     /**
  41      * Factored code for invoking a bootstrap method for invokedynamic
  42      * or a dynamic constant.
  43      * @param resultType the expected return type (either CallSite or a constant type)
  44      * @param bootstrapMethod the BSM to call
  45      * @param name the method name or constant name
  46      * @param type the method type or constant type
  47      * @param info information passed up from the JVM, to derive static arguments
  48      * @param callerClass the class containing the resolved method call or constant load
  49      * @param <T> the expected return type
  50      * @return the expected value, either a CallSite or a constant value
  51      */
  52     static <T> T invoke(Class<T> resultType,
  53                         MethodHandle bootstrapMethod,
  54                         // Callee information:
  55                         String name, Object type,
  56                         // Extra arguments for BSM, if any:
  57                         Object info,
  58                         // Caller information:
  59                         Class<?> callerClass) {
  60         MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
  61         Object result;
  62         boolean pullMode = isPullModeBSM(bootstrapMethod);  // default value is false
  63         boolean vmIsPushing = !staticArgumentsPulled(info); // default value is true
  64         MethodHandle pullModeBSM;
  65         // match the VM with the BSM
  66         if (vmIsPushing) {
  67             // VM is pushing arguments at us
  68             pullModeBSM = null;
  69             if (pullMode) {
  70                 bootstrapMethod = pushMePullYou(bootstrapMethod, true);
  71             }
  72         } else {
  73             // VM wants us to pull args from it
  74             pullModeBSM = pullMode ? bootstrapMethod :
  75                     pushMePullYou(bootstrapMethod, false);
  76             bootstrapMethod = null;
  77         }
  78         try {
  79             info = maybeReBox(info);
  80             if (info == null) {
  81                 // VM is allowed to pass up a null meaning no BSM args
  82                 result = bootstrapMethod.invoke(caller, name, type);
  83             }
  84             else if (!info.getClass().isArray()) {
  85                 // VM is allowed to pass up a single BSM arg directly
  86                 result = bootstrapMethod.invoke(caller, name, type, info);
  87             }
  88             else if (info.getClass() == int[].class) {
  89                 // VM is allowed to pass up a pair {argc, index}
  90                 // referring to 'argc' BSM args at some place 'index'
  91                 // in the guts of the VM (associated with callerClass).
  92                 // The format of this index pair is private to the
  93                 // handshake between the VM and this class only.
  94                 // This supports "pulling" of arguments.
  95                 // The VM is allowed to do this for any reason.
  96                 // The code in this method makes up for any mismatches.
  97                 BootstrapCallInfo<Object> bsci
  98                     = new VM_BSCI<>(bootstrapMethod, name, type, caller, (int[])info);
  99                 // Pull-mode API is (Lookup, BootstrapCallInfo) -> Object
 100                 result = pullModeBSM.invoke(caller, bsci);
 101             }
 102             else {
 103                 // VM is allowed to pass up a full array of resolved BSM args
 104                 Object[] argv = (Object[]) info;
 105                 maybeReBoxElements(argv);
 106                 switch (argv.length) {
 107                     case 0:
 108                         result = bootstrapMethod.invoke(caller, name, type);
 109                         break;
 110                     case 1:
 111                         result = bootstrapMethod.invoke(caller, name, type,
 112                                                         argv[0]);
 113                         break;
 114                     case 2:
 115                         result = bootstrapMethod.invoke(caller, name, type,
 116                                                         argv[0], argv[1]);
 117                         break;
 118                     case 3:
 119                         // Special case the LambdaMetafactory::metafactory BSM
 120                         //
 121                         // By invoking exactly, we can avoid generating a number of
 122                         // classes on first (and subsequent) lambda initialization,
 123                         // most of which won't be shared with other invoke uses.
 124                         MethodType bsmType = bootstrapMethod.type();
 125                         if (isLambdaMetafactoryIndyBSM(bsmType)) {
 126                             result = (CallSite)bootstrapMethod
 127                                     .invokeExact(caller, name, (MethodType)type, (MethodType)argv[0],
 128                                                  (MethodHandle)argv[1], (MethodType)argv[2]);
 129                         } else if (isLambdaMetafactoryCondyBSM(bsmType)) {
 130                             result = bootstrapMethod
 131                                     .invokeExact(caller, name, (Class<?>)type, (MethodType)argv[0],
 132                                                  (MethodHandle)argv[1], (MethodType)argv[2]);
 133                         } else {
 134                             result = bootstrapMethod.invoke(caller, name, type,
 135                                                             argv[0], argv[1], argv[2]);
 136                         }
 137                         break;
 138                     case 4:
 139                         result = bootstrapMethod.invoke(caller, name, type,
 140                                                         argv[0], argv[1], argv[2], argv[3]);
 141                         break;
 142                     case 5:
 143                         result = bootstrapMethod.invoke(caller, name, type,
 144                                                         argv[0], argv[1], argv[2], argv[3], argv[4]);
 145                         break;
 146                     case 6:
 147                         result = bootstrapMethod.invoke(caller, name, type,
 148                                                         argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
 149                         break;
 150                     default:
 151                         final int NON_SPREAD_ARG_COUNT = 3;  // (caller, name, type)
 152                         final int MAX_SAFE_SIZE = MethodType.MAX_MH_ARITY / 2 - NON_SPREAD_ARG_COUNT;
 153                         if (argv.length >= MAX_SAFE_SIZE) {
 154                             // to be on the safe side, use invokeWithArguments which handles jumbo lists
 155                             Object[] newargv = new Object[NON_SPREAD_ARG_COUNT + argv.length];
 156                             newargv[0] = caller;
 157                             newargv[1] = name;
 158                             newargv[2] = type;
 159                             System.arraycopy(argv, 0, newargv, NON_SPREAD_ARG_COUNT, argv.length);
 160                             result = bootstrapMethod.invokeWithArguments(newargv);
 161                             break;
 162                         }
 163                         MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
 164                         MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
 165                         MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
 166                         result = spreader.invokeExact(typedBSM, (Object) caller, (Object) name, type, argv);
 167                 }
 168             }
 169             if (resultType.isPrimitive()) {
 170                 // Non-reference conversions are more than just plain casts.
 171                 // By pushing the value through a funnel of the form (T x)->x,
 172                 // the boxed result can be widened as needed.  See MH::asType.
 173                 MethodHandle funnel = MethodHandles.identity(resultType);
 174                 result = funnel.invoke(result);
 175                 // Now it is the wrapper type for resultType.
 176                 resultType = Wrapper.asWrapperType(resultType);
 177             }
 178             return resultType.cast(result);
 179         }
 180         catch (Error e) {
 181             // Pass through an Error, including BootstrapMethodError, any other
 182             // form of linkage error, such as IllegalAccessError if the bootstrap
 183             // method is inaccessible, or say ThreadDeath/OutOfMemoryError
 184             // See the "Linking Exceptions" section for the invokedynamic
 185             // instruction in JVMS 6.5.
 186             throw e;
 187         }
 188         catch (Throwable ex) {
 189             // Wrap anything else in BootstrapMethodError
 190             throw new BootstrapMethodError("bootstrap method initialization exception", ex);
 191         }
 192     }
 193 
 194     private static final MethodType LMF_INDY_MT = MethodType.methodType(CallSite.class,
 195             Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class);
 196 
 197     private static final MethodType LMF_CONDY_MT = MethodType.methodType(Object.class,
 198             Lookup.class, String.class, Class.class, MethodType.class, MethodHandle.class, MethodType.class);
 199 
 200     /**
 201      * @return true iff the BSM method type exactly matches
 202      *         {@see java.lang.invoke.LambdaMetafactory#metafactory(
 203      *          MethodHandles.Lookup,String,Class,MethodType,MethodHandle,MethodType)}
 204      */
 205     private static boolean isLambdaMetafactoryCondyBSM(MethodType bsmType) {
 206         return bsmType == LMF_CONDY_MT;
 207     }
 208 
 209     /**
 210      * @return true iff the BSM method type exactly matches
 211      *         {@see java.lang.invoke.LambdaMetafactory#metafactory(
 212      *          MethodHandles.Lookup,String,MethodType,MethodType,MethodHandle,MethodType)}
 213      */
 214     private static boolean isLambdaMetafactoryIndyBSM(MethodType bsmType) {
 215         return bsmType == LMF_INDY_MT;
 216     }
 217 
 218     /** The JVM produces java.lang.Integer values to box
 219      *  CONSTANT_Integer boxes but does not intern them.
 220      *  Let's intern them.  This is slightly wrong for
 221      *  a {@code CONSTANT_Dynamic} which produces an
 222      *  un-interned integer (e.g., {@code new Integer(0)}).
 223      */
 224     private static Object maybeReBox(Object x) {
 225         if (x instanceof Integer) {
 226             int xi = (int) x;
 227             if (xi == (byte) xi)
 228                 x = xi;  // must rebox; see JLS 5.1.7
 229         }
 230         return x;
 231     }
 232 
 233     private static void maybeReBoxElements(Object[] xa) {
 234         for (int i = 0; i < xa.length; i++) {
 235             xa[i] = maybeReBox(xa[i]);
 236         }
 237     }
 238 
 239     /** Canonical VM-aware implementation of BootstrapCallInfo.
 240      * Knows how to dig into the JVM for lazily resolved (pull-mode) constants.
 241      */
 242     private static final class VM_BSCI<T> extends BSCIWithCache<T> {
 243         private final int[] indexInfo;
 244         private final Class<?> caller;  // for index resolution only
 245 
 246         VM_BSCI(MethodHandle bsm, String name, T type,
 247                 Lookup lookup, int[] indexInfo) {
 248             super(bsm, name, type, indexInfo[0]);
 249             if (!lookup.hasPrivateAccess())  //D.I.D.
 250                 throw new AssertionError("bad Lookup object");
 251             this.caller = lookup.lookupClass();
 252             this.indexInfo = indexInfo;
 253             // scoop up all the easy stuff right away:
 254             prefetchIntoCache(0, size());
 255         }
 256 
 257         @Override Object fillCache(int i) {
 258             Object[] buf = { null };
 259             copyConstants(i, i+1, buf, 0);
 260             Object res = wrapNull(buf[0]);
 261             cache[i] = res;
 262             int next = i + 1;
 263             if (next < cache.length && cache[next] == null)
 264                 maybePrefetchIntoCache(next, false);  // try to prefetch
 265             return res;
 266         }
 267 
 268         @Override public int copyConstants(int start, int end,
 269                                            Object[] buf, int pos) {
 270             int i = start, bufi = pos;
 271             while (i < end) {
 272                 Object x = cache[i];
 273                 if (x == null)  break;
 274                 buf[bufi++] = unwrapNull(x);
 275                 i++;
 276             }
 277             // give up at first null and grab the rest in one big block
 278             if (i >= end)  return i;
 279             Object[] temp = new Object[end - i];
 280             if (TRACE_METHOD_LINKAGE) {
 281                 System.out.println("resolving more BSM arguments: " +
 282                         Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, end));
 283             }
 284             copyOutBootstrapArguments(caller, indexInfo,
 285                                       i, end, temp, 0,
 286                                       true, null);
 287             for (Object x : temp) {
 288                 x = maybeReBox(x);
 289                 buf[bufi++] = x;
 290                 cache[i++] = wrapNull(x);
 291             }
 292             if (end < cache.length && cache[end] == null)
 293                 maybePrefetchIntoCache(end, true);  // try to prefetch
 294             return i;
 295         }
 296 
 297         private static final int MIN_PF = 4;
 298         private void maybePrefetchIntoCache(int i, boolean bulk) {
 299             int len = cache.length;
 300             assert(0 <= i && i <= len);
 301             int pfLimit = i;
 302             if (bulk)  pfLimit += i;  // exponential prefetch expansion
 303             // try to prefetch at least MIN_PF elements
 304             if (pfLimit < i + MIN_PF)  pfLimit = i + MIN_PF;
 305             if (pfLimit > len || pfLimit < 0)  pfLimit = len;
 306             // stop prefetching where cache is more full than empty
 307             int empty = 0, nonEmpty = 0, lastEmpty = i;
 308             for (int j = i; j < pfLimit; j++) {
 309                 if (cache[j] == null) {
 310                     empty++;
 311                     lastEmpty = j;
 312                 } else {
 313                     nonEmpty++;
 314                     if (nonEmpty > empty) {
 315                         pfLimit = lastEmpty + 1;
 316                         break;
 317                     }
 318                     if (pfLimit < len)  pfLimit++;
 319                 }
 320             }
 321             if (bulk && empty < MIN_PF && pfLimit < len)
 322                 return;  // not worth the effort
 323             prefetchIntoCache(i, pfLimit);
 324         }
 325 
 326         private void prefetchIntoCache(int i, int pfLimit) {
 327             if (pfLimit <= i)  return;  // corner case
 328             Object[] temp = new Object[pfLimit - i];
 329             if (TRACE_METHOD_LINKAGE) {
 330                 System.out.println("prefetching BSM arguments: " +
 331                         Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, pfLimit));
 332             }
 333             copyOutBootstrapArguments(caller, indexInfo,
 334                                       i, pfLimit, temp, 0,
 335                                       false, NOT_PRESENT);
 336             for (Object x : temp) {
 337                 if (x != NOT_PRESENT && cache[i] == null) {
 338                     cache[i] = wrapNull(maybeReBox(x));
 339                 }
 340                 i++;
 341             }
 342         }
 343     }
 344 
 345     /*non-public*/ static final
 346     class PushAdapter {
 347         // skeleton for push-mode BSM which wraps a pull-mode BSM:
 348         static Object pushToBootstrapMethod(MethodHandle pullModeBSM,
 349                                             MethodHandles.Lookup lookup, String name, Object type,
 350                                             Object... arguments) throws Throwable {
 351             ConstantGroup cons = makeConstantGroup(Arrays.asList(arguments));
 352             BootstrapCallInfo<?> bsci = makeBootstrapCallInfo(pullModeBSM, name, type, cons);
 353             if (TRACE_METHOD_LINKAGE)
 354                 System.out.println("pull-mode BSM gets pushed arguments from fake BSCI");
 355             return pullModeBSM.invoke(lookup, bsci);
 356         }
 357 
 358         static final MethodHandle MH_pushToBootstrapMethod;
 359         static {
 360             final Class<?> THIS_CLASS = PushAdapter.class;
 361             try {
 362                 MH_pushToBootstrapMethod = IMPL_LOOKUP
 363                     .findStatic(THIS_CLASS, "pushToBootstrapMethod",
 364                                 MethodType.methodType(Object.class, MethodHandle.class,
 365                                         Lookup.class, String.class, Object.class, Object[].class));
 366             } catch (Throwable ex) {
 367                 throw new InternalError(ex);
 368             }
 369         }
 370     }
 371 
 372     /*non-public*/ static final
 373     class PullAdapter {
 374         // skeleton for pull-mode BSM which wraps a push-mode BSM:
 375         static Object pullFromBootstrapMethod(MethodHandle pushModeBSM,
 376                                               MethodHandles.Lookup lookup,
 377                                               BootstrapCallInfo<?> bsci)
 378                 throws Throwable {
 379             int argc = bsci.size();
 380             switch (argc) {
 381                 case 0:
 382                     return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType());
 383                 case 1:
 384                     return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(),
 385                             bsci.get(0));
 386                 case 2:
 387                     return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(),
 388                             bsci.get(0), bsci.get(1));
 389                 case 3:
 390                     return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(),
 391                             bsci.get(0), bsci.get(1), bsci.get(2));
 392                 case 4:
 393                     return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(),
 394                             bsci.get(0), bsci.get(1), bsci.get(2), bsci.get(3));
 395                 case 5:
 396                     return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(),
 397                             bsci.get(0), bsci.get(1), bsci.get(2), bsci.get(3), bsci.get(4));
 398                 case 6:
 399                     return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(),
 400                             bsci.get(0), bsci.get(1), bsci.get(2), bsci.get(3), bsci.get(4), bsci.get(5));
 401                 default:
 402                     final int NON_SPREAD_ARG_COUNT = 3;  // (lookup, name, type)
 403                     final int MAX_SAFE_SIZE = MethodType.MAX_MH_ARITY / 2 - NON_SPREAD_ARG_COUNT;
 404                     if (argc >= MAX_SAFE_SIZE) {
 405                         // to be on the safe side, use invokeWithArguments which handles jumbo lists
 406                         Object[] newargv = new Object[NON_SPREAD_ARG_COUNT + argc];
 407                         newargv[0] = lookup;
 408                         newargv[1] = bsci.invocationName();
 409                         newargv[2] = bsci.invocationType();
 410                         bsci.copyConstants(0, argc, newargv, NON_SPREAD_ARG_COUNT);
 411                         return pushModeBSM.invokeWithArguments(newargv);
 412                     }
 413                     MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argc);
 414                     MethodHandle typedBSM = pushModeBSM.asType(invocationType);
 415                     MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
 416                     Object[] argv = new Object[argc];
 417                     bsci.copyConstants(0, argc, argv, 0);
 418                     return spreader.invokeExact(typedBSM, (Object) lookup, (Object) bsci.invocationName(), bsci.invocationType(), argv);
 419                 }
 420         }
 421 
 422         static final MethodHandle MH_pullFromBootstrapMethod;
 423 
 424         static {
 425             final Class<?> THIS_CLASS = PullAdapter.class;
 426             try {
 427                 MH_pullFromBootstrapMethod = IMPL_LOOKUP
 428                     .findStatic(THIS_CLASS, "pullFromBootstrapMethod",
 429                                 MethodType.methodType(Object.class, MethodHandle.class,
 430                                         Lookup.class, BootstrapCallInfo.class));
 431             } catch (Throwable ex) {
 432                 throw new InternalError(ex);
 433             }
 434         }
 435     }
 436 
 437     /** Given a push-mode BSM (taking one argument) convert it to a
 438      *  pull-mode BSM (taking N pre-resolved arguments).
 439      *  This method is used when, in fact, the JVM is passing up
 440      *  pre-resolved arguments, but the BSM is expecting lazy stuff.
 441      *  Or, when goToPushMode is true, do the reverse transform.
 442      *  (The two transforms are exactly inverse.)
 443      */
 444     static MethodHandle pushMePullYou(MethodHandle bsm, boolean goToPushMode) {
 445         if (TRACE_METHOD_LINKAGE) {
 446             System.out.println("converting BSM of type " + bsm.type() + " to "
 447                     + (goToPushMode ? "push mode" : "pull mode"));
 448         }
 449         assert(isPullModeBSM(bsm) == goToPushMode); // there must be a change
 450         if (goToPushMode) {
 451             return PushAdapter.MH_pushToBootstrapMethod.bindTo(bsm).withVarargs(true);
 452         } else {
 453             return PullAdapter.MH_pullFromBootstrapMethod.bindTo(bsm).withVarargs(false);
 454         }
 455     }
 456 }