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