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 = Adapters.pushMePullYou(bootstrapMethod, true);
  71             }
  72         } else {
  73             // VM wants us to pull args from it
  74             pullModeBSM = pullMode ? bootstrapMethod :
  75                     Adapters.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                         result = bootstrapMethod.invoke(caller, name, type,
 120                                                         argv[0], argv[1], argv[2]);
 121                         break;
 122                     case 4:
 123                         result = bootstrapMethod.invoke(caller, name, type,
 124                                                         argv[0], argv[1], argv[2], argv[3]);
 125                         break;
 126                     case 5:
 127                         result = bootstrapMethod.invoke(caller, name, type,
 128                                                         argv[0], argv[1], argv[2], argv[3], argv[4]);
 129                         break;
 130                     case 6:
 131                         result = bootstrapMethod.invoke(caller, name, type,
 132                                                         argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
 133                         break;
 134                     default:
 135                         final int NON_SPREAD_ARG_COUNT = 3;  // (caller, name, type)
 136                         final int MAX_SAFE_SIZE = MethodType.MAX_MH_ARITY / 2 - NON_SPREAD_ARG_COUNT;
 137                         if (argv.length >= MAX_SAFE_SIZE) {
 138                             // to be on the safe side, use invokeWithArguments which handles jumbo lists
 139                             Object[] newargv = new Object[NON_SPREAD_ARG_COUNT + argv.length];
 140                             newargv[0] = caller;
 141                             newargv[1] = name;
 142                             newargv[2] = type;
 143                             System.arraycopy(argv, 0, newargv, NON_SPREAD_ARG_COUNT, argv.length);
 144                             result = bootstrapMethod.invokeWithArguments(newargv);
 145                             break;
 146                         }
 147                         MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
 148                         MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
 149                         MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
 150                         result = spreader.invokeExact(typedBSM, (Object) caller, (Object) name, type, argv);
 151                 }
 152             }
 153             if (resultType.isPrimitive()) {
 154                 // Non-reference conversions are more than just plain casts.
 155                 // By pushing the value through a funnel of the form (T x)->x,
 156                 // the boxed result can be widened as needed.  See MH::asType.
 157                 MethodHandle funnel = MethodHandles.identity(resultType);
 158                 result = funnel.invoke(result);
 159                 // Now it is the wrapper type for resultType.
 160                 resultType = Wrapper.asWrapperType(resultType);
 161             }
 162             return resultType.cast(result);
 163         }
 164         catch (Error e) {
 165             // Pass through an Error, including BootstrapMethodError, any other
 166             // form of linkage error, such as IllegalAccessError if the bootstrap
 167             // method is inaccessible, or say ThreadDeath/OutOfMemoryError
 168             // See the "Linking Exceptions" section for the invokedynamic
 169             // instruction in JVMS 6.5.
 170             throw e;
 171         }
 172         catch (Throwable ex) {
 173             // Wrap anything else in BootstrapMethodError
 174             throw new BootstrapMethodError("bootstrap method initialization exception", ex);
 175         }
 176     }
 177 
 178     /** The JVM produces java.lang.Integer values to box
 179      *  CONSTANT_Integer boxes but does not intern them.
 180      *  Let's intern them.  This is slightly wrong for
 181      *  a {@code CONSTANT_Dynamic} which produces an
 182      *  un-interned integer (e.g., {@code new Integer(0)}).
 183      */
 184     private static Object maybeReBox(Object x) {
 185         if (x instanceof Integer) {
 186             int xi = (int) x;
 187             if (xi == (byte) xi)
 188                 x = xi;  // must rebox; see JLS 5.1.7
 189         }
 190         return x;
 191     }
 192 
 193     private static void maybeReBoxElements(Object[] xa) {
 194         for (int i = 0; i < xa.length; i++) {
 195             xa[i] = maybeReBox(xa[i]);
 196         }
 197     }
 198 
 199     /** Canonical VM-aware implementation of BootstrapCallInfo.
 200      * Knows how to dig into the JVM for lazily resolved (pull-mode) constants.
 201      */
 202     private static final class VM_BSCI<T> extends BSCIWithCache<T> {
 203         private final int[] indexInfo;
 204         private final Class<?> caller;  // for index resolution only
 205 
 206         VM_BSCI(MethodHandle bsm, String name, T type,
 207                 Lookup lookup, int[] indexInfo) {
 208             super(bsm, name, type, indexInfo[0]);
 209             if (!lookup.hasPrivateAccess())  //D.I.D.
 210                 throw new AssertionError("bad Lookup object");
 211             this.caller = lookup.lookupClass();
 212             this.indexInfo = indexInfo;
 213             // scoop up all the easy stuff right away:
 214             prefetchIntoCache(0, size());
 215         }
 216 
 217         @Override Object fillCache(int i) {
 218             Object[] buf = { null };
 219             copyConstants(i, i+1, buf, 0);
 220             Object res = wrapNull(buf[0]);
 221             cache[i] = res;
 222             int next = i + 1;
 223             if (next < cache.length && cache[next] == null)
 224                 maybePrefetchIntoCache(next, false);  // try to prefetch
 225             return res;
 226         }
 227 
 228         @Override public int copyConstants(int start, int end,
 229                                            Object[] buf, int pos) {
 230             int i = start, bufi = pos;
 231             while (i < end) {
 232                 Object x = cache[i];
 233                 if (x == null)  break;
 234                 buf[bufi++] = unwrapNull(x);
 235                 i++;
 236             }
 237             // give up at first null and grab the rest in one big block
 238             if (i >= end)  return i;
 239             Object[] temp = new Object[end - i];
 240             if (TRACE_METHOD_LINKAGE)
 241                 System.out.println("resolving more BSM arguments: "+
 242                         Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, end));
 243             copyOutBootstrapArguments(caller, indexInfo,
 244                                       i, end, temp, 0,
 245                                       true, null);
 246             for (Object x : temp) {
 247                 x = maybeReBox(x);
 248                 buf[bufi++] = x;
 249                 cache[i++] = wrapNull(x);
 250             }
 251             if (end < cache.length && cache[end] == null)
 252                 maybePrefetchIntoCache(end, true);  // try to prefetch
 253             return i;
 254         }
 255 
 256         private static final int MIN_PF = 4;
 257         private void maybePrefetchIntoCache(int i, boolean bulk) {
 258             int len = cache.length;
 259             assert(0 <= i && i <= len);
 260             int pfLimit = i;
 261             if (bulk)  pfLimit += i;  // exponential prefetch expansion
 262             // try to prefetch at least MIN_PF elements
 263             if (pfLimit < i + MIN_PF)  pfLimit = i + MIN_PF;
 264             if (pfLimit > len || pfLimit < 0)  pfLimit = len;
 265             // stop prefetching where cache is more full than empty
 266             int empty = 0, nonEmpty = 0, lastEmpty = i;
 267             for (int j = i; j < pfLimit; j++) {
 268                 if (cache[j] == null) {
 269                     empty++;
 270                     lastEmpty = j;
 271                 } else {
 272                     nonEmpty++;
 273                     if (nonEmpty > empty) {
 274                         pfLimit = lastEmpty + 1;
 275                         break;
 276                     }
 277                     if (pfLimit < len)  pfLimit++;
 278                 }
 279             }
 280             if (bulk && empty < MIN_PF && pfLimit < len)
 281                 return;  // not worth the effort
 282             prefetchIntoCache(i, pfLimit);
 283         }
 284 
 285         private void prefetchIntoCache(int i, int pfLimit) {
 286             if (pfLimit <= i)  return;  // corner case
 287             Object[] temp = new Object[pfLimit - i];
 288             if (TRACE_METHOD_LINKAGE)
 289                 System.out.println("prefetching BSM arguments: "+
 290                         Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, pfLimit));
 291             copyOutBootstrapArguments(caller, indexInfo,
 292                                       i, pfLimit, temp, 0,
 293                                       false, NOT_PRESENT);
 294             for (Object x : temp) {
 295                 if (x != NOT_PRESENT && cache[i] == null) {
 296                     cache[i] = wrapNull(maybeReBox(x));
 297                 }
 298                 i++;
 299             }
 300         }
 301     }
 302 
 303     /*non-public*/ static final
 304     class Adapters {
 305         // skeleton for push-mode BSM which wraps a pull-mode BSM:
 306         static Object pushToBootstrapMethod(MethodHandle pullModeBSM,
 307                                             MethodHandles.Lookup lookup, String name, Object type,
 308                                             Object... arguments) throws Throwable {
 309             ConstantGroup cons = makeConstantGroup(Arrays.asList(arguments));
 310             BootstrapCallInfo<?> bsci = makeBootstrapCallInfo(pullModeBSM, name, type, cons);
 311             if (TRACE_METHOD_LINKAGE)
 312                 System.out.println("pull-mode BSM gets pushed arguments from fake BSCI");
 313             return pullModeBSM.invoke(lookup, bsci);
 314         }
 315 
 316         // skeleton for pull-mode BSM which wraps a push-mode BSM:
 317         static Object pullFromBootstrapMethod(MethodHandle pushModeBSM,
 318                                               MethodHandles.Lookup lookup, BootstrapCallInfo<?> bsci)
 319                 throws Throwable {
 320             int argc = bsci.size();
 321             Object arguments[] = new Object[3 + argc];
 322             arguments[0] = lookup;
 323             arguments[1] = bsci.invocationName();
 324             arguments[2] = bsci.invocationType();
 325             bsci.copyConstants(0, argc, arguments, 3);
 326             if (TRACE_METHOD_LINKAGE)
 327                 System.out.println("pulled arguments from VM for push-mode BSM");
 328             return pushModeBSM.invokeWithArguments(arguments);
 329         }
 330         static final MethodHandle MH_pushToBootstrapMethod;
 331         static final MethodHandle MH_pullFromBootstrapMethod;
 332         static {
 333             final Class<?> THIS_CLASS = Adapters.class;
 334             try {
 335                 MH_pushToBootstrapMethod = IMPL_LOOKUP
 336                     .findStatic(THIS_CLASS, "pushToBootstrapMethod",
 337                                 MethodType.methodType(Object.class, MethodHandle.class,
 338                                         Lookup.class, String.class, Object.class, Object[].class));
 339                 MH_pullFromBootstrapMethod = IMPL_LOOKUP
 340                     .findStatic(THIS_CLASS, "pullFromBootstrapMethod",
 341                                 MethodType.methodType(Object.class, MethodHandle.class,
 342                                         Lookup.class, BootstrapCallInfo.class));
 343             } catch (Throwable ex) {
 344                 throw new InternalError(ex);
 345             }
 346         }
 347 
 348         /** Given a push-mode BSM (taking one argument) convert it to a
 349          *  pull-mode BSM (taking N pre-resolved arguments).
 350          *  This method is used when, in fact, the JVM is passing up
 351          *  pre-resolved arguments, but the BSM is expecting lazy stuff.
 352          *  Or, when goToPushMode is true, do the reverse transform.
 353          *  (The two transforms are exactly inverse.)
 354          */
 355         static MethodHandle pushMePullYou(MethodHandle bsm, boolean goToPushMode) {
 356             if (TRACE_METHOD_LINKAGE)
 357                 System.out.println("converting BSM to "+(goToPushMode ? "push mode" : "pull mode"));
 358             assert(isPullModeBSM(bsm) == goToPushMode);  //there must be a change
 359             if (goToPushMode) {
 360                 return Adapters.MH_pushToBootstrapMethod.bindTo(bsm).withVarargs(true);
 361             } else {
 362                 return Adapters.MH_pullFromBootstrapMethod.bindTo(bsm).withVarargs(false);
 363             }
 364         }
 365     }
 366 }