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