MethodHandleFactory.java revision 953:221a84ef44c0
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 /** 120 * Tracer that is applied before a value is returned from the traced function. It will output the return 121 * value and its class 122 * 123 * @param value return value for filter 124 * @return return value unmodified 125 */ 126 static Object traceReturn(final DebugLogger logger, final Object value) { 127 if (logger.isEnabled()) { 128 final String str = " return" + 129 (VOID_TAG.equals(value) ? 130 ";" : 131 " " + stripName(value) + "; // [type=" + (value == null ? "null]" : stripName(value.getClass()) + ']')); 132 logger.log(TRACE_LEVEL, str); 133 } 134 return value; 135 } 136 137 static void traceReturnVoid(final DebugLogger logger) { 138 traceReturn(logger, VOID_TAG); 139 } 140 141 /** 142 * Tracer that is applied before a function is called, printing the arguments 143 * 144 * @param tag tag to start the debug printout string 145 * @param paramStart param index to start outputting from 146 * @param args arguments to the function 147 */ 148 static void traceArgs(final DebugLogger logger, final String tag, final int paramStart, final Object... args) { 149 final StringBuilder sb = new StringBuilder(); 150 151 sb.append(tag); 152 153 for (int i = paramStart; i < args.length; i++) { 154 if (i == paramStart) { 155 sb.append(" => args: "); 156 } 157 158 sb.append('\''). 159 append(stripName(argString(args[i]))). 160 append('\''). 161 append(' '). 162 append('['). 163 append("type="). 164 append(args[i] == null ? "null" : stripName(args[i].getClass())). 165 append(']'); 166 167 if (i + 1 < args.length) { 168 sb.append(", "); 169 } 170 } 171 172 assert logger != null; 173 logger.log(TRACE_LEVEL, sb); 174 stacktrace(logger); 175 } 176 177 private static void stacktrace(final DebugLogger logger) { 178 if (!PRINT_STACKTRACE) { 179 return; 180 } 181 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 182 final PrintStream ps = new PrintStream(baos); 183 new Throwable().printStackTrace(ps); 184 logger.log(TRACE_LEVEL, baos.toString()); 185 } 186 187 private static String argString(final Object arg) { 188 if (arg == null) { 189 return "null"; 190 } 191 192 if (arg.getClass().isArray()) { 193 final List<Object> list = new ArrayList<>(); 194 for (final Object elem : (Object[])arg) { 195 list.add('\'' + argString(elem) + '\''); 196 } 197 198 return list.toString(); 199 } 200 201 if (arg instanceof ScriptObject) { 202 return arg.toString() + 203 " (map=" + Debug.id(((ScriptObject)arg).getMap()) + 204 ")"; 205 } 206 207 return arg.toString(); 208 } 209 210 /** 211 * Add a debug printout to a method handle, tracing parameters and return values 212 * 213 * @param logger a specific logger to which to write the output 214 * @param level level over which to print 215 * @param mh method handle to trace 216 * @param tag start of trace message 217 * @return traced method handle 218 */ 219 public static MethodHandle addDebugPrintout(final DebugLogger logger, final Level level, final MethodHandle mh, final Object tag) { 220 return addDebugPrintout(logger, level, mh, 0, true, tag); 221 } 222 223 /** 224 * Add a debug printout to a method handle, tracing parameters and return values 225 * 226 * @param logger a specific logger to which to write the output 227 * @param level level over which to print 228 * @param mh method handle to trace 229 * @param paramStart first param to print/trace 230 * @param printReturnValue should we print/trace return value if available? 231 * @param tag start of trace message 232 * @return traced method handle 233 */ 234 public static MethodHandle addDebugPrintout(final DebugLogger logger, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Object tag) { 235 final MethodType type = mh.type(); 236 237 //if there is no logger, or if it's set to log only coarser events 238 //than the trace level, skip and return 239 if (logger != null && logger.levelCoarserThan(level)) { 240 return mh; 241 } 242 243 assert logger != null; 244 assert TRACE != null; 245 246 MethodHandle trace = MethodHandles.insertArguments(TRACE, 0, logger, tag, paramStart); 247 248 trace = MethodHandles.foldArguments( 249 mh, 250 trace.asCollector( 251 Object[].class, 252 type.parameterCount()). 253 asType(type.changeReturnType(void.class))); 254 255 final Class<?> retType = type.returnType(); 256 if (printReturnValue) { 257 if (retType != void.class) { 258 final MethodHandle traceReturn = MethodHandles.insertArguments(TRACE_RETURN, 0, logger); 259 trace = MethodHandles.filterReturnValue(trace, 260 traceReturn.asType( 261 traceReturn.type().changeParameterType(0, retType).changeReturnType(retType))); 262 } else { 263 trace = MethodHandles.filterReturnValue(trace, MethodHandles.insertArguments(TRACE_RETURN_VOID, 0, logger)); 264 } 265 } 266 267 return trace; 268 } 269 270 /** 271 * Class that marshalls all method handle operations to the java.lang.invoke 272 * package. This exists only so that it can be subclassed and method handles created from 273 * Nashorn made possible to instrument. 274 * 275 * All Nashorn classes should use the MethodHandleFactory for their method handle operations 276 */ 277 @Logger(name="methodhandles") 278 private static class StandardMethodHandleFunctionality implements MethodHandleFunctionality, Loggable { 279 280 // for bootstrapping reasons, because a lot of static fields use MH for lookups, we 281 // need to set the logger when the Global object is finished. This means that we don't 282 // get instrumentation for public static final MethodHandle SOMETHING = MH... in the builtin 283 // classes, but that doesn't matter, because this is usually not where we want it 284 private DebugLogger log = DebugLogger.DISABLED_LOGGER; 285 286 public StandardMethodHandleFunctionality() { 287 } 288 289 @Override 290 public DebugLogger initLogger(final Context context) { 291 return this.log = context.getLogger(this.getClass()); 292 } 293 294 @Override 295 public DebugLogger getLogger() { 296 return log; 297 } 298 299 protected static String describe(final Object... data) { 300 final StringBuilder sb = new StringBuilder(); 301 302 for (int i = 0; i < data.length; i++) { 303 final Object d = data[i]; 304 if (d == null) { 305 sb.append("<null> "); 306 } else if (d instanceof String || d instanceof ConsString) { 307 sb.append(d.toString()); 308 sb.append(' '); 309 } else if (d.getClass().isArray()) { 310 sb.append("[ "); 311 for (final Object da : (Object[])d) { 312 sb.append(describe(new Object[]{ da })).append(' '); 313 } 314 sb.append("] "); 315 } else { 316 sb.append(d) 317 .append('{') 318 .append(Integer.toHexString(System.identityHashCode(d))) 319 .append('}'); 320 } 321 322 if (i + 1 < data.length) { 323 sb.append(", "); 324 } 325 } 326 327 return sb.toString(); 328 } 329 330 public MethodHandle debug(final MethodHandle master, final String str, final Object... args) { 331 if (log.isEnabled()) { 332 if (PRINT_STACKTRACE) { 333 stacktrace(log); 334 } 335 return addDebugPrintout(log, Level.INFO, master, Integer.MAX_VALUE, false, str + ' ' + describe(args)); 336 } 337 return master; 338 } 339 340 @Override 341 public MethodHandle filterArguments(final MethodHandle target, final int pos, final MethodHandle... filters) { 342 final MethodHandle mh = MethodHandles.filterArguments(target, pos, filters); 343 return debug(mh, "filterArguments", target, pos, filters); 344 } 345 346 @Override 347 public MethodHandle filterReturnValue(final MethodHandle target, final MethodHandle filter) { 348 final MethodHandle mh = MethodHandles.filterReturnValue(target, filter); 349 return debug(mh, "filterReturnValue", target, filter); 350 } 351 352 @Override 353 public MethodHandle guardWithTest(final MethodHandle test, final MethodHandle target, final MethodHandle fallback) { 354 final MethodHandle mh = MethodHandles.guardWithTest(test, target, fallback); 355 return debug(mh, "guardWithTest", test, target, fallback); 356 } 357 358 @Override 359 public MethodHandle insertArguments(final MethodHandle target, final int pos, final Object... values) { 360 final MethodHandle mh = MethodHandles.insertArguments(target, pos, values); 361 return debug(mh, "insertArguments", target, pos, values); 362 } 363 364 @Override 365 public MethodHandle dropArguments(final MethodHandle target, final int pos, final Class<?>... values) { 366 final MethodHandle mh = MethodHandles.dropArguments(target, pos, values); 367 return debug(mh, "dropArguments", target, pos, values); 368 } 369 370 @Override 371 public MethodHandle dropArguments(final MethodHandle target, final int pos, final List<Class<?>> values) { 372 final MethodHandle mh = MethodHandles.dropArguments(target, pos, values); 373 return debug(mh, "dropArguments", target, pos, values); 374 } 375 376 @Override 377 public MethodHandle asType(final MethodHandle handle, final MethodType type) { 378 final MethodHandle mh = handle.asType(type); 379 return debug(mh, "asType", handle, type); 380 } 381 382 @Override 383 public MethodHandle bindTo(final MethodHandle handle, final Object x) { 384 final MethodHandle mh = handle.bindTo(x); 385 return debug(mh, "bindTo", handle, x); 386 } 387 388 @Override 389 public MethodHandle foldArguments(final MethodHandle target, final MethodHandle combiner) { 390 final MethodHandle mh = MethodHandles.foldArguments(target, combiner); 391 return debug(mh, "foldArguments", target, combiner); 392 } 393 394 @Override 395 public MethodHandle explicitCastArguments(final MethodHandle target, final MethodType type) { 396 final MethodHandle mh = MethodHandles.explicitCastArguments(target, type); 397 return debug(mh, "explicitCastArguments", target, type); 398 } 399 400 @Override 401 public MethodHandle arrayElementGetter(final Class<?> type) { 402 final MethodHandle mh = MethodHandles.arrayElementGetter(type); 403 return debug(mh, "arrayElementGetter", type); 404 } 405 406 @Override 407 public MethodHandle arrayElementSetter(final Class<?> type) { 408 final MethodHandle mh = MethodHandles.arrayElementSetter(type); 409 return debug(mh, "arrayElementSetter", type); 410 } 411 412 @Override 413 public MethodHandle throwException(final Class<?> returnType, final Class<? extends Throwable> exType) { 414 final MethodHandle mh = MethodHandles.throwException(returnType, exType); 415 return debug(mh, "throwException", returnType, exType); 416 } 417 418 @Override 419 public MethodHandle catchException(final MethodHandle target, final Class<? extends Throwable> exType, final MethodHandle handler) { 420 final MethodHandle mh = MethodHandles.catchException(target, exType, handler); 421 return debug(mh, "catchException", exType); 422 } 423 424 @Override 425 public MethodHandle constant(final Class<?> type, final Object value) { 426 final MethodHandle mh = MethodHandles.constant(type, value); 427 return debug(mh, "constant", type, value); 428 } 429 430 @Override 431 public MethodHandle asCollector(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) { 432 final MethodHandle mh = handle.asCollector(arrayType, arrayLength); 433 return debug(mh, "asCollector", handle, arrayType, arrayLength); 434 } 435 436 @Override 437 public MethodHandle asSpreader(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) { 438 final MethodHandle mh = handle.asSpreader(arrayType, arrayLength); 439 return debug(mh, "asSpreader", handle, arrayType, arrayLength); 440 } 441 442 @Override 443 public MethodHandle getter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) { 444 try { 445 final MethodHandle mh = explicitLookup.findGetter(clazz, name, type); 446 return debug(mh, "getter", explicitLookup, clazz, name, type); 447 } catch (final NoSuchFieldException | IllegalAccessException e) { 448 throw new LookupException(e); 449 } 450 } 451 452 @Override 453 public MethodHandle staticGetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) { 454 try { 455 final MethodHandle mh = explicitLookup.findStaticGetter(clazz, name, type); 456 return debug(mh, "static getter", explicitLookup, clazz, name, type); 457 } catch (final NoSuchFieldException | IllegalAccessException e) { 458 throw new LookupException(e); 459 } 460 } 461 462 @Override 463 public MethodHandle setter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) { 464 try { 465 final MethodHandle mh = explicitLookup.findSetter(clazz, name, type); 466 return debug(mh, "setter", explicitLookup, clazz, name, type); 467 } catch (final NoSuchFieldException | IllegalAccessException e) { 468 throw new LookupException(e); 469 } 470 } 471 472 @Override 473 public MethodHandle staticSetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) { 474 try { 475 final MethodHandle mh = explicitLookup.findStaticSetter(clazz, name, type); 476 return debug(mh, "static setter", explicitLookup, clazz, name, type); 477 } catch (final NoSuchFieldException | IllegalAccessException e) { 478 throw new LookupException(e); 479 } 480 } 481 482 @Override 483 public MethodHandle find(final Method method) { 484 try { 485 final MethodHandle mh = PUBLIC_LOOKUP.unreflect(method); 486 return debug(mh, "find", method); 487 } catch (final IllegalAccessException e) { 488 throw new LookupException(e); 489 } 490 } 491 492 @Override 493 public MethodHandle findStatic(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) { 494 try { 495 final MethodHandle mh = explicitLookup.findStatic(clazz, name, type); 496 return debug(mh, "findStatic", explicitLookup, clazz, name, type); 497 } catch (final NoSuchMethodException | IllegalAccessException e) { 498 throw new LookupException(e); 499 } 500 } 501 502 @Override 503 public MethodHandle findSpecial(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type, final Class<?> thisClass) { 504 try { 505 final MethodHandle mh = explicitLookup.findSpecial(clazz, name, type, thisClass); 506 return debug(mh, "findSpecial", explicitLookup, clazz, name, type); 507 } catch (final NoSuchMethodException | IllegalAccessException e) { 508 throw new LookupException(e); 509 } 510 } 511 512 @Override 513 public MethodHandle findVirtual(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) { 514 try { 515 final MethodHandle mh = explicitLookup.findVirtual(clazz, name, type); 516 return debug(mh, "findVirtual", explicitLookup, clazz, name, type); 517 } catch (final NoSuchMethodException | IllegalAccessException e) { 518 throw new LookupException(e); 519 } 520 } 521 522 @Override 523 public SwitchPoint createSwitchPoint() { 524 final SwitchPoint sp = new SwitchPoint(); 525 log.log(TRACE_LEVEL, "createSwitchPoint ", sp); 526 return sp; 527 } 528 529 @Override 530 public MethodHandle guardWithTest(final SwitchPoint sp, final MethodHandle before, final MethodHandle after) { 531 final MethodHandle mh = sp.guardWithTest(before, after); 532 return debug(mh, "guardWithTest", sp, before, after); 533 } 534 535 @Override 536 public MethodType type(final Class<?> returnType, final Class<?>... paramTypes) { 537 final MethodType mt = MethodType.methodType(returnType, paramTypes); 538 log.log(TRACE_LEVEL, "methodType ", returnType, " ", Arrays.toString(paramTypes), " ", mt); 539 return mt; 540 } 541 } 542} 543