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