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 26package jdk.nashorn.internal.lookup; 27 28import static jdk.nashorn.internal.runtime.JSType.isString; 29 30import java.io.ByteArrayOutputStream; 31import java.io.PrintStream; 32import java.lang.invoke.MethodHandle; 33import java.lang.invoke.MethodHandles; 34import java.lang.invoke.MethodType; 35import java.lang.invoke.SwitchPoint; 36import java.lang.reflect.Method; 37import java.util.ArrayList; 38import java.util.Arrays; 39import java.util.List; 40import java.util.logging.Level; 41import jdk.nashorn.internal.runtime.Context; 42import jdk.nashorn.internal.runtime.Debug; 43import jdk.nashorn.internal.runtime.ScriptObject; 44import jdk.nashorn.internal.runtime.logging.DebugLogger; 45import jdk.nashorn.internal.runtime.logging.Loggable; 46import jdk.nashorn.internal.runtime.logging.Logger; 47import 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 */ 59public 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} 590