1 /*
   2  * Copyright (c) 2010, 2013, 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 
  26 package jdk.nashorn.internal.lookup;
  27 
  28 import static jdk.nashorn.internal.runtime.JSType.isString;
  29 
  30 import java.io.ByteArrayOutputStream;
  31 import java.io.PrintStream;
  32 import java.lang.invoke.MethodHandle;
  33 import java.lang.invoke.MethodHandles;
  34 import java.lang.invoke.MethodType;
  35 import java.lang.invoke.SwitchPoint;
  36 import java.lang.reflect.Method;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.List;
  40 import java.util.logging.Level;
  41 import jdk.nashorn.internal.runtime.Context;
  42 import jdk.nashorn.internal.runtime.Debug;
  43 import jdk.nashorn.internal.runtime.ScriptObject;
  44 import jdk.nashorn.internal.runtime.logging.DebugLogger;
  45 import jdk.nashorn.internal.runtime.logging.Loggable;
  46 import jdk.nashorn.internal.runtime.logging.Logger;
  47 import jdk.nashorn.internal.runtime.options.Options;
  48 
  49 /**
  50  * This class is abstraction for all method handle, switchpoint and method type
  51  * operations. This enables the functionality interface to be subclassed and
  52  * instrumented, as it has been proven vital to keep the number of method
  53  * handles in the system down.
  54  *
  55  * All operations of the above type should go through this class, and not
  56  * directly into java.lang.invoke
  57  *
  58  */
  59 public final class MethodHandleFactory {
  60 
  61     private static final MethodHandles.Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup();
  62     private static final MethodHandles.Lookup LOOKUP        = MethodHandles.lookup();
  63 
  64     private static final Level TRACE_LEVEL = Level.INFO;
  65 
  66     private MethodHandleFactory() {
  67     }
  68 
  69     /**
  70      * Runtime exception that collects every reason that a method handle lookup operation can go wrong
  71      */
  72     @SuppressWarnings("serial")
  73     public static class LookupException extends RuntimeException {
  74         /**
  75          * Constructor
  76          * @param e causing exception
  77          */
  78         public LookupException(final Exception e) {
  79             super(e);
  80         }
  81     }
  82 
  83     /**
  84      * Helper function that takes a class or an object with a toString override
  85      * and shortens it to notation after last dot. This is used to facilitiate
  86      * pretty printouts in various debug loggers - internal only
  87      *
  88      * @param obj class or object
  89      *
  90      * @return pretty version of object as string
  91      */
  92     public static String stripName(final Object obj) {
  93         if (obj == null) {
  94             return "null";
  95         }
  96 
  97         if (obj instanceof Class) {
  98             return ((Class<?>)obj).getSimpleName();
  99         }
 100         return obj.toString();
 101     }
 102 
 103     private static final MethodHandleFunctionality FUNC = new StandardMethodHandleFunctionality();
 104     private static final boolean PRINT_STACKTRACE = Options.getBooleanProperty("nashorn.methodhandles.debug.stacktrace");
 105 
 106     /**
 107      * Return the method handle functionality used for all method handle operations
 108      * @return a method handle functionality implementation
 109      */
 110     public static MethodHandleFunctionality getFunctionality() {
 111         return FUNC;
 112     }
 113 
 114     private static final MethodHandle TRACE             = FUNC.findStatic(LOOKUP, MethodHandleFactory.class, "traceArgs",   MethodType.methodType(void.class, DebugLogger.class, String.class, int.class, Object[].class));
 115     private static final MethodHandle TRACE_RETURN      = FUNC.findStatic(LOOKUP, MethodHandleFactory.class, "traceReturn", MethodType.methodType(Object.class, DebugLogger.class, Object.class));
 116     private static final MethodHandle TRACE_RETURN_VOID = FUNC.findStatic(LOOKUP, MethodHandleFactory.class, "traceReturnVoid", MethodType.methodType(void.class, DebugLogger.class));
 117 
 118     private static final String VOID_TAG = "[VOID]";
 119 
 120     private static void err(final String str) {
 121         Context.getContext().getErr().println(str);
 122     }
 123 
 124     /**
 125      * Tracer that is applied before a value is returned from the traced function. It will output the return
 126      * value and its class
 127      *
 128      * @param value return value for filter
 129      * @return return value unmodified
 130      */
 131     static Object traceReturn(final DebugLogger logger, final Object value) {
 132         final String str = "    return" +
 133                 (VOID_TAG.equals(value) ?
 134                         ";" :
 135                             " " + stripName(value) + "; // [type=" + (value == null ? "null]" : stripName(value.getClass()) + ']'));
 136         if (logger == null) {
 137             err(str);
 138         } else if (logger.isEnabled()) {
 139             logger.log(TRACE_LEVEL, str);
 140         }
 141 
 142         return value;
 143     }
 144 
 145     static void traceReturnVoid(final DebugLogger logger) {
 146         traceReturn(logger, VOID_TAG);
 147     }
 148 
 149     /**
 150      * Tracer that is applied before a function is called, printing the arguments
 151      *
 152      * @param tag  tag to start the debug printout string
 153      * @param paramStart param index to start outputting from
 154      * @param args arguments to the function
 155      */
 156     static void traceArgs(final DebugLogger logger, final String tag, final int paramStart, final Object... args) {
 157         final StringBuilder sb = new StringBuilder();
 158 
 159         sb.append(tag);
 160 
 161         for (int i = paramStart; i < args.length; i++) {
 162             if (i == paramStart) {
 163                 sb.append(" => args: ");
 164             }
 165 
 166             sb.append('\'').
 167             append(stripName(argString(args[i]))).
 168             append('\'').
 169             append(' ').
 170             append('[').
 171             append("type=").
 172             append(args[i] == null ? "null" : stripName(args[i].getClass())).
 173             append(']');
 174 
 175             if (i + 1 < args.length) {
 176                 sb.append(", ");
 177             }
 178         }
 179 
 180         if (logger == null) {
 181             err(sb.toString());
 182         } else {
 183             logger.log(TRACE_LEVEL, sb);
 184         }
 185         stacktrace(logger);
 186     }
 187 
 188     private static void stacktrace(final DebugLogger logger) {
 189         if (!PRINT_STACKTRACE) {
 190             return;
 191         }
 192         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 193         final PrintStream ps = new PrintStream(baos);
 194         new Throwable().printStackTrace(ps);
 195         final String st = baos.toString();
 196         if (logger == null) {
 197             err(st);
 198         } else {
 199             logger.log(TRACE_LEVEL, st);
 200         }
 201     }
 202 
 203     private static String argString(final Object arg) {
 204         if (arg == null) {
 205             return "null";
 206         }
 207 
 208         if (arg.getClass().isArray()) {
 209             final List<Object> list = new ArrayList<>();
 210             for (final Object elem : (Object[])arg) {
 211                 list.add('\'' + argString(elem) + '\'');
 212             }
 213 
 214             return list.toString();
 215         }
 216 
 217         if (arg instanceof ScriptObject) {
 218             return arg.toString() +
 219                     " (map=" + Debug.id(((ScriptObject)arg).getMap()) +
 220                     ')';
 221         }
 222 
 223         return arg.toString();
 224     }
 225 
 226     /**
 227      * Add a debug printout to a method handle, tracing parameters and return values
 228      * Output will be unconditional to stderr
 229      *
 230      * @param mh  method handle to trace
 231      * @param tag start of trace message
 232      * @return traced method handle
 233      */
 234     public static MethodHandle addDebugPrintout(final MethodHandle mh, final Object tag) {
 235         return addDebugPrintout(null, Level.OFF, mh, 0, true, tag);
 236     }
 237 
 238     /**
 239      * Add a debug printout to a method handle, tracing parameters and return values
 240      *
 241      * @param logger a specific logger to which to write the output
 242      * @param level level over which to print
 243      * @param mh  method handle to trace
 244      * @param tag start of trace message
 245      * @return traced method handle
 246      */
 247     public static MethodHandle addDebugPrintout(final DebugLogger logger, final Level level, final MethodHandle mh, final Object tag) {
 248         return addDebugPrintout(logger, level, mh, 0, true, tag);
 249     }
 250 
 251     /**
 252      * Add a debug printout to a method handle, tracing parameters and return values
 253      * Output will be unconditional to stderr
 254      *
 255      * @param mh  method handle to trace
 256      * @param paramStart first param to print/trace
 257      * @param printReturnValue should we print/trace return value if available?
 258      * @param tag start of trace message
 259      * @return  traced method handle
 260      */
 261     public static MethodHandle addDebugPrintout(final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Object tag) {
 262         return addDebugPrintout(null, Level.OFF, mh, paramStart, printReturnValue, tag);
 263     }
 264 
 265     /**
 266      * Add a debug printout to a method handle, tracing parameters and return values
 267      *
 268      * @param logger a specific logger to which to write the output
 269      * @param level level over which to print
 270      * @param mh  method handle to trace
 271      * @param paramStart first param to print/trace
 272      * @param printReturnValue should we print/trace return value if available?
 273      * @param tag start of trace message
 274      * @return  traced method handle
 275      */
 276     public static MethodHandle addDebugPrintout(final DebugLogger logger, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Object tag) {
 277         final MethodType type = mh.type();
 278 
 279         //if there is no logger, or if it's set to log only coarser events
 280         //than the trace level, skip and return
 281         if (logger == null || !logger.isLoggable(level)) {
 282             return mh;
 283         }
 284 
 285         assert TRACE != null;
 286 
 287         MethodHandle trace = MethodHandles.insertArguments(TRACE, 0, logger, tag, paramStart);
 288 
 289         trace = MethodHandles.foldArguments(
 290                 mh,
 291                 trace.asCollector(
 292                         Object[].class,
 293                         type.parameterCount()).
 294                         asType(type.changeReturnType(void.class)));
 295 
 296         final Class<?> retType = type.returnType();
 297         if (printReturnValue) {
 298             if (retType != void.class) {
 299                 final MethodHandle traceReturn = MethodHandles.insertArguments(TRACE_RETURN, 0, logger);
 300                 trace = MethodHandles.filterReturnValue(trace,
 301                         traceReturn.asType(
 302                                 traceReturn.type().changeParameterType(0, retType).changeReturnType(retType)));
 303             } else {
 304                 trace = MethodHandles.filterReturnValue(trace, MethodHandles.insertArguments(TRACE_RETURN_VOID, 0, logger));
 305             }
 306         }
 307 
 308         return trace;
 309     }
 310 
 311     /**
 312      * Class that marshalls all method handle operations to the java.lang.invoke
 313      * package. This exists only so that it can be subclassed and method handles created from
 314      * Nashorn made possible to instrument.
 315      *
 316      * All Nashorn classes should use the MethodHandleFactory for their method handle operations
 317      */
 318     @Logger(name="methodhandles")
 319     private static class StandardMethodHandleFunctionality implements MethodHandleFunctionality, Loggable {
 320 
 321         // for bootstrapping reasons, because a lot of static fields use MH for lookups, we
 322         // need to set the logger when the Global object is finished. This means that we don't
 323         // get instrumentation for public static final MethodHandle SOMETHING = MH... in the builtin
 324         // classes, but that doesn't matter, because this is usually not where we want it
 325         private DebugLogger log = DebugLogger.DISABLED_LOGGER;
 326 
 327         public StandardMethodHandleFunctionality() {
 328         }
 329 
 330         @Override
 331         public DebugLogger initLogger(final Context context) {
 332             return this.log = context.getLogger(this.getClass());
 333         }
 334 
 335         @Override
 336         public DebugLogger getLogger() {
 337             return log;
 338         }
 339 
 340         protected static String describe(final Object... data) {
 341             final StringBuilder sb = new StringBuilder();
 342 
 343             for (int i = 0; i < data.length; i++) {
 344                 final Object d = data[i];
 345                 if (d == null) {
 346                     sb.append("<null> ");
 347                 } else if (isString(d)) {
 348                     sb.append(d.toString());
 349                     sb.append(' ');
 350                 } else if (d.getClass().isArray()) {
 351                     sb.append("[ ");
 352                     for (final Object da : (Object[])d) {
 353                         sb.append(describe(new Object[]{ da })).append(' ');
 354                     }
 355                     sb.append("] ");
 356                 } else {
 357                     sb.append(d)
 358                     .append('{')
 359                     .append(Integer.toHexString(System.identityHashCode(d)))
 360                     .append('}');
 361                 }
 362 
 363                 if (i + 1 < data.length) {
 364                     sb.append(", ");
 365                 }
 366             }
 367 
 368             return sb.toString();
 369         }
 370 
 371         public MethodHandle debug(final MethodHandle master, final String str, final Object... args) {
 372             if (log.isEnabled()) {
 373                 if (PRINT_STACKTRACE) {
 374                     stacktrace(log);
 375                 }
 376                 return addDebugPrintout(log, Level.INFO, master, Integer.MAX_VALUE, false, str + ' ' + describe(args));
 377             }
 378             return master;
 379         }
 380 
 381         @Override
 382         public MethodHandle filterArguments(final MethodHandle target, final int pos, final MethodHandle... filters) {
 383             final MethodHandle mh = MethodHandles.filterArguments(target, pos, filters);
 384             return debug(mh, "filterArguments", target, pos, filters);
 385         }
 386 
 387         @Override
 388         public MethodHandle filterReturnValue(final MethodHandle target, final MethodHandle filter) {
 389             final MethodHandle mh = MethodHandles.filterReturnValue(target, filter);
 390             return debug(mh, "filterReturnValue", target, filter);
 391         }
 392 
 393         @Override
 394         public MethodHandle guardWithTest(final MethodHandle test, final MethodHandle target, final MethodHandle fallback) {
 395             final MethodHandle mh = MethodHandles.guardWithTest(test, target, fallback);
 396             return debug(mh, "guardWithTest", test, target, fallback);
 397         }
 398 
 399         @Override
 400         public MethodHandle insertArguments(final MethodHandle target, final int pos, final Object... values) {
 401             final MethodHandle mh = MethodHandles.insertArguments(target, pos, values);
 402             return debug(mh, "insertArguments", target, pos, values);
 403         }
 404 
 405         @Override
 406         public MethodHandle dropArguments(final MethodHandle target, final int pos, final Class<?>... values) {
 407             final MethodHandle mh = MethodHandles.dropArguments(target, pos, values);
 408             return debug(mh, "dropArguments", target, pos, values);
 409         }
 410 
 411         @Override
 412         public MethodHandle dropArguments(final MethodHandle target, final int pos, final List<Class<?>> values) {
 413             final MethodHandle mh = MethodHandles.dropArguments(target, pos, values);
 414             return debug(mh, "dropArguments", target, pos, values);
 415         }
 416 
 417         @Override
 418         public MethodHandle asType(final MethodHandle handle, final MethodType type) {
 419             final MethodHandle mh = handle.asType(type);
 420             return debug(mh, "asType", handle, type);
 421         }
 422 
 423         @Override
 424         public MethodHandle bindTo(final MethodHandle handle, final Object x) {
 425             final MethodHandle mh = handle.bindTo(x);
 426             return debug(mh, "bindTo", handle, x);
 427         }
 428 
 429         @Override
 430         public MethodHandle foldArguments(final MethodHandle target, final MethodHandle combiner) {
 431             final MethodHandle mh = MethodHandles.foldArguments(target, combiner);
 432             return debug(mh, "foldArguments", target, combiner);
 433         }
 434 
 435         @Override
 436         public MethodHandle explicitCastArguments(final MethodHandle target, final MethodType type) {
 437             final MethodHandle mh = MethodHandles.explicitCastArguments(target, type);
 438             return debug(mh, "explicitCastArguments", target, type);
 439         }
 440 
 441         @Override
 442         public MethodHandle arrayElementGetter(final Class<?> type) {
 443             final MethodHandle mh = MethodHandles.arrayElementGetter(type);
 444             return debug(mh, "arrayElementGetter", type);
 445         }
 446 
 447         @Override
 448         public MethodHandle arrayElementSetter(final Class<?> type) {
 449             final MethodHandle mh = MethodHandles.arrayElementSetter(type);
 450             return debug(mh, "arrayElementSetter", type);
 451         }
 452 
 453         @Override
 454         public MethodHandle throwException(final Class<?> returnType, final Class<? extends Throwable> exType) {
 455             final MethodHandle mh = MethodHandles.throwException(returnType, exType);
 456             return debug(mh, "throwException", returnType, exType);
 457         }
 458 
 459         @Override
 460         public MethodHandle catchException(final MethodHandle target, final Class<? extends Throwable> exType, final MethodHandle handler) {
 461             final MethodHandle mh = MethodHandles.catchException(target, exType, handler);
 462             return debug(mh, "catchException", exType);
 463         }
 464 
 465         @Override
 466         public MethodHandle constant(final Class<?> type, final Object value) {
 467             final MethodHandle mh = MethodHandles.constant(type, value);
 468             return debug(mh, "constant", type, value);
 469         }
 470 
 471         @Override
 472         public MethodHandle identity(final Class<?> type) {
 473             final MethodHandle mh = MethodHandles.identity(type);
 474             return debug(mh, "identity", type);
 475         }
 476 
 477         @Override
 478         public MethodHandle asCollector(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) {
 479             final MethodHandle mh = handle.asCollector(arrayType, arrayLength);
 480             return debug(mh, "asCollector", handle, arrayType, arrayLength);
 481         }
 482 
 483         @Override
 484         public MethodHandle asSpreader(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) {
 485             final MethodHandle mh = handle.asSpreader(arrayType, arrayLength);
 486             return debug(mh, "asSpreader", handle, arrayType, arrayLength);
 487         }
 488 
 489         @Override
 490         public MethodHandle getter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
 491             try {
 492                 final MethodHandle mh = explicitLookup.findGetter(clazz, name, type);
 493                 return debug(mh, "getter", explicitLookup, clazz, name, type);
 494             } catch (final NoSuchFieldException | IllegalAccessException e) {
 495                 throw new LookupException(e);
 496             }
 497         }
 498 
 499         @Override
 500         public MethodHandle staticGetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
 501             try {
 502                 final MethodHandle mh = explicitLookup.findStaticGetter(clazz, name, type);
 503                 return debug(mh, "static getter", explicitLookup, clazz, name, type);
 504             } catch (final NoSuchFieldException | IllegalAccessException e) {
 505                 throw new LookupException(e);
 506             }
 507         }
 508 
 509         @Override
 510         public MethodHandle setter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
 511             try {
 512                 final MethodHandle mh = explicitLookup.findSetter(clazz, name, type);
 513                 return debug(mh, "setter", explicitLookup, clazz, name, type);
 514             } catch (final NoSuchFieldException | IllegalAccessException e) {
 515                 throw new LookupException(e);
 516             }
 517         }
 518 
 519         @Override
 520         public MethodHandle staticSetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
 521             try {
 522                 final MethodHandle mh = explicitLookup.findStaticSetter(clazz, name, type);
 523                 return debug(mh, "static setter", explicitLookup, clazz, name, type);
 524             } catch (final NoSuchFieldException | IllegalAccessException e) {
 525                 throw new LookupException(e);
 526             }
 527         }
 528 
 529         @Override
 530         public MethodHandle find(final Method method) {
 531             try {
 532                 final MethodHandle mh = PUBLIC_LOOKUP.unreflect(method);
 533                 return debug(mh, "find", method);
 534             } catch (final IllegalAccessException e) {
 535                 throw new LookupException(e);
 536             }
 537         }
 538 
 539         @Override
 540         public MethodHandle findStatic(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) {
 541             try {
 542                 final MethodHandle mh = explicitLookup.findStatic(clazz, name, type);
 543                 return debug(mh, "findStatic", explicitLookup, clazz, name, type);
 544             } catch (final NoSuchMethodException | IllegalAccessException e) {
 545                 throw new LookupException(e);
 546             }
 547         }
 548 
 549         @Override
 550         public MethodHandle findSpecial(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type, final Class<?> thisClass) {
 551             try {
 552                 final MethodHandle mh = explicitLookup.findSpecial(clazz, name, type, thisClass);
 553                 return debug(mh, "findSpecial", explicitLookup, clazz, name, type);
 554             } catch (final NoSuchMethodException | IllegalAccessException e) {
 555                 throw new LookupException(e);
 556             }
 557         }
 558 
 559         @Override
 560         public MethodHandle findVirtual(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) {
 561             try {
 562                 final MethodHandle mh = explicitLookup.findVirtual(clazz, name, type);
 563                 return debug(mh, "findVirtual", explicitLookup, clazz, name, type);
 564             } catch (final NoSuchMethodException | IllegalAccessException e) {
 565                 throw new LookupException(e);
 566             }
 567         }
 568 
 569         @Override
 570         public SwitchPoint createSwitchPoint() {
 571             final SwitchPoint sp = new SwitchPoint();
 572             log.log(TRACE_LEVEL, "createSwitchPoint ", sp);
 573             return sp;
 574         }
 575 
 576         @Override
 577         public MethodHandle guardWithTest(final SwitchPoint sp, final MethodHandle before, final MethodHandle after) {
 578             final MethodHandle mh = sp.guardWithTest(before, after);
 579             return debug(mh, "guardWithTest", sp, before, after);
 580         }
 581 
 582         @Override
 583         public MethodType type(final Class<?> returnType, final Class<?>... paramTypes) {
 584             final MethodType mt = MethodType.methodType(returnType, paramTypes);
 585             log.log(TRACE_LEVEL, "methodType ", returnType, " ", Arrays.toString(paramTypes), " ", mt);
 586             return mt;
 587         }
 588     }
 589 }