aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/lookup/MethodHandleFactory.java')
-rw-r--r--src/jdk/nashorn/internal/lookup/MethodHandleFactory.java646
1 files changed, 646 insertions, 0 deletions
diff --git a/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java b/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java
new file mode 100644
index 00000000..2161c782
--- /dev/null
+++ b/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java
@@ -0,0 +1,646 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.lookup;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.SwitchPoint;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import jdk.nashorn.internal.runtime.ConsString;
+import jdk.nashorn.internal.runtime.Debug;
+import jdk.nashorn.internal.runtime.DebugLogger;
+import jdk.nashorn.internal.runtime.ScriptObject;
+import jdk.nashorn.internal.runtime.options.Options;
+
+/**
+ * This class is abstraction for all method handle, switchpoint and method type
+ * operations. This enables the functionality interface to be subclassed and
+ * intrumensted, as it has been proven vital to keep the number of method
+ * handles in the system down.
+ *
+ * All operations of the above type should go through this class, and not
+ * directly into java.lang.invoke
+ *
+ */
+public final class MethodHandleFactory {
+
+ private static final MethodHandles.Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup();
+ private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+
+ private static final Level TRACE_LEVEL = Level.INFO;
+
+ private MethodHandleFactory() {
+ }
+
+ /**
+ * Runtime exception that collects every reason that a method handle lookup operation can go wrong
+ */
+ @SuppressWarnings("serial")
+ public static class LookupException extends RuntimeException {
+ /**
+ * Constructor
+ * @param e causing exception
+ */
+ public LookupException(final Exception e) {
+ super(e);
+ }
+ }
+
+ /**
+ * Helper function that takes a class or an object with a toString override
+ * and shortens it to notation after last dot. This is used to facilitiate
+ * pretty printouts in various debug loggers - internal only
+ *
+ * @param obj class or object
+ *
+ * @return pretty version of object as string
+ */
+ public static String stripName(final Object obj) {
+ if (obj == null) {
+ return "null";
+ }
+
+ if (obj instanceof Class) {
+ return ((Class<?>)obj).getSimpleName();
+ }
+ return obj.toString();
+ }
+
+ private static final MethodHandleFunctionality STANDARD = new StandardMethodHandleFunctionality();
+ private static final MethodHandleFunctionality FUNC;
+
+ private static final String DEBUG_PROPERTY = "nashorn.methodhandles.debug";
+ private static final DebugLogger LOG = new DebugLogger("methodhandles", DEBUG_PROPERTY);
+
+ static {
+ if (LOG.isEnabled() || Options.getBooleanProperty(DEBUG_PROPERTY)) {
+ if (Options.getStringProperty(DEBUG_PROPERTY, "").equals("create")) {
+ FUNC = new TraceCreateMethodHandleFunctionality();
+ } else {
+ FUNC = new TraceMethodHandleFunctionality();
+ }
+ } else {
+ FUNC = STANDARD;
+ }
+ }
+
+ private static final boolean PRINT_STACKTRACE = Options.getBooleanProperty("nashorn.methodhandles.debug.stacktrace");
+
+
+ /**
+ * Return the method handle functionality used for all method handle operations
+ * @return a method handle functionality implementation
+ */
+ public static MethodHandleFunctionality getFunctionality() {
+ return FUNC;
+ }
+
+ private static final MethodHandle TRACE = STANDARD.findStatic(LOOKUP, MethodHandleFactory.class, "traceArgs", MethodType.methodType(void.class, DebugLogger.class, String.class, int.class, Object[].class));
+ private static final MethodHandle TRACE_RETURN = STANDARD.findStatic(LOOKUP, MethodHandleFactory.class, "traceReturn", MethodType.methodType(Object.class, DebugLogger.class, Object.class));
+
+ /**
+ * Tracer that is applied before a value is returned from the traced function. It will output the return
+ * value and its class
+ *
+ * @param value return value for filter
+ * @return return value unmodified
+ */
+ static Object traceReturn(final DebugLogger logger, final Object value) {
+ final String str = "\treturn: " + stripName(value) + " [type=" + (value == null ? "null" : stripName(value.getClass()) + ']');
+ logger.log(str, TRACE_LEVEL);
+ return value;
+ }
+
+ /**
+ * Tracer that is applied before a function is called, printing the arguments
+ *
+ * @param tag tag to start the debug printout string
+ * @param paramStart param index to start outputting from
+ * @param args arguments to the function
+ */
+ static void traceArgs(final DebugLogger logger, final String tag, final int paramStart, final Object... args) {
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append(tag);
+
+ for (int i = paramStart; i < args.length; i++) {
+ if (i == paramStart) {
+ sb.append(" => args: ");
+ }
+
+ sb.append('\'').
+ append(stripName(argString(args[i]))).
+ append('\'').
+ append(' ').
+ append('[').
+ append("type=").
+ append(args[i] == null ? "null" : stripName(args[i].getClass())).
+ append(']');
+
+ if (i + 1 < args.length) {
+ sb.append(", ");
+ }
+ }
+
+ assert logger != null;
+ logger.log(sb.toString(), TRACE_LEVEL);
+ stacktrace(logger);
+ }
+
+ private static void stacktrace(final DebugLogger logger) {
+ if (!PRINT_STACKTRACE) {
+ return;
+ }
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final PrintStream ps = new PrintStream(baos);
+ new Throwable().printStackTrace(ps);
+ logger.log(baos.toString(), TRACE_LEVEL);
+ }
+
+ private static String argString(final Object arg) {
+ if (arg == null) {
+ return "null";
+ }
+
+ if (arg.getClass().isArray()) {
+ final List<Object> list = new ArrayList<>();
+ for (final Object elem : (Object[])arg) {
+ list.add('\'' + argString(elem) + '\'');
+ }
+
+ return list.toString();
+ }
+
+ if (arg instanceof ScriptObject) {
+ return arg.toString() +
+ " (map=" + Debug.id((((ScriptObject)arg).getMap())) +
+ ")";
+ }
+
+ return arg.toString();
+ }
+
+ /**
+ * Add a debug printout to a method handle, tracing parameters and return values
+ *
+ * @param logger a specific logger to which to write the output
+ * @param mh method handle to trace
+ * @param tag start of trace message
+ * @return traced method handle
+ */
+ public static MethodHandle addDebugPrintout(final DebugLogger logger, final MethodHandle mh, final Object tag) {
+ return addDebugPrintout(logger, mh, 0, true, tag);
+ }
+
+
+ /**
+ * Add a debug printout to a method handle, tracing parameters and return values
+ *
+ * @param logger a specific logger to which to write the output
+ * @param mh method handle to trace
+ * @param paramStart first param to print/trace
+ * @param printReturnValue should we print/trace return value if available?
+ * @param tag start of trace message
+ * @return traced method handle
+ */
+ public static MethodHandle addDebugPrintout(final DebugLogger logger, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Object tag) {
+ final MethodType type = mh.type();
+
+ if (logger != null && logger.levelAbove(TRACE_LEVEL)) {
+ return mh;
+ }
+
+ assert logger != null;
+ assert TRACE != null;
+
+ MethodHandle trace = MethodHandles.insertArguments(TRACE, 0, logger, tag, paramStart);
+
+ trace = MethodHandles.foldArguments(
+ mh,
+ trace.asCollector(
+ Object[].class,
+ type.parameterCount()).
+ asType(type.changeReturnType(void.class)));
+
+ final Class<?> retType = type.returnType();
+ if (retType != void.class && printReturnValue) {
+ final MethodHandle traceReturn = MethodHandles.insertArguments(TRACE_RETURN, 0, logger);
+ trace = MethodHandles.filterReturnValue(trace,
+ traceReturn.asType(
+ traceReturn.type().changeParameterType(0, retType).changeReturnType(retType)));
+ }
+
+ return trace;
+ }
+
+ /**
+ * The standard class that marshalls all method handle operations to the java.lang.invoke
+ * package. This exists only so that it can be subclassed and method handles created from
+ * Nashorn made possible to instrument.
+ *
+ * All Nashorn classes should use the MethodHandleFactory for their method handle operations
+ */
+ private static class StandardMethodHandleFunctionality implements MethodHandleFunctionality {
+
+ @Override
+ public MethodHandle filterArguments(final MethodHandle target, final int pos, final MethodHandle... filters) {
+ return MethodHandles.filterArguments(target, pos, filters);
+ }
+
+ @Override
+ public MethodHandle filterReturnValue(final MethodHandle target, final MethodHandle filter) {
+ return MethodHandles.filterReturnValue(target, filter);
+ }
+
+ @Override
+ public MethodHandle guardWithTest(final MethodHandle test, final MethodHandle target, final MethodHandle fallback) {
+ return MethodHandles.guardWithTest(test, target, fallback);
+ }
+
+ @Override
+ public MethodHandle insertArguments(final MethodHandle target, final int pos, final Object... values) {
+ return MethodHandles.insertArguments(target, pos, values);
+ }
+
+ @Override
+ public MethodHandle dropArguments(final MethodHandle target, final int pos, final Class<?>... valueTypes) {
+ return MethodHandles.dropArguments(target, pos, valueTypes);
+ }
+
+ @Override
+ public MethodHandle dropArguments(final MethodHandle target, final int pos, final List<Class<?>> valueTypes) {
+ return MethodHandles.dropArguments(target, pos, valueTypes);
+ }
+
+ @Override
+ public MethodHandle asType(final MethodHandle handle, final MethodType type) {
+ return handle.asType(type);
+ }
+
+ @Override
+ public MethodHandle bindTo(final MethodHandle handle, final Object x) {
+ return handle.bindTo(x);
+ }
+
+ @Override
+ public MethodHandle foldArguments(final MethodHandle target, final MethodHandle combiner) {
+ return MethodHandles.foldArguments(target, combiner);
+ }
+
+ @Override
+ public MethodHandle explicitCastArguments(final MethodHandle target, final MethodType type) {
+ return MethodHandles.explicitCastArguments(target, type);
+ }
+
+ @Override
+ public MethodHandle arrayElementGetter(final Class<?> type) {
+ return MethodHandles.arrayElementGetter(type);
+ }
+
+ @Override
+ public MethodHandle arrayElementSetter(final Class<?> type) {
+ return MethodHandles.arrayElementSetter(type);
+ }
+
+ @Override
+ public MethodHandle throwException(final Class<?> returnType, final Class<? extends Throwable> exType) {
+ return MethodHandles.throwException(returnType, exType);
+ }
+
+ @Override
+ public MethodHandle constant(final Class<?> type, final Object value) {
+ return MethodHandles.constant(type, value);
+ }
+
+ @Override
+ public MethodHandle asCollector(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) {
+ return handle.asCollector(arrayType, arrayLength);
+ }
+
+ @Override
+ public MethodHandle asSpreader(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) {
+ return handle.asSpreader(arrayType, arrayLength);
+ }
+
+ @Override
+ public MethodHandle getter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ try {
+ return explicitLookup.findGetter(clazz, name, type);
+ } catch (final NoSuchFieldException | IllegalAccessException e) {
+ throw new LookupException(e);
+ }
+ }
+
+ @Override
+ public MethodHandle staticGetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ try {
+ return explicitLookup.findStaticGetter(clazz, name, type);
+ } catch (final NoSuchFieldException | IllegalAccessException e) {
+ throw new LookupException(e);
+ }
+ }
+
+
+ @Override
+ public MethodHandle setter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ try {
+ return explicitLookup.findSetter(clazz, name, type);
+ } catch (final NoSuchFieldException | IllegalAccessException e) {
+ throw new LookupException(e);
+ }
+ }
+
+ @Override
+ public MethodHandle staticSetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ try {
+ return explicitLookup.findStaticSetter(clazz, name, type);
+ } catch (final NoSuchFieldException | IllegalAccessException e) {
+ throw new LookupException(e);
+ }
+ }
+
+ @Override
+ public MethodHandle find(final Method method) {
+ try {
+ return PUBLIC_LOOKUP.unreflect(method);
+ } catch (final IllegalAccessException e) {
+ throw new LookupException(e);
+ }
+ }
+
+ @Override
+ public MethodHandle findStatic(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) {
+ try {
+ return explicitLookup.findStatic(clazz, name, type);
+ } catch (final NoSuchMethodException | IllegalAccessException e) {
+ throw new LookupException(e);
+ }
+ }
+
+ @Override
+ public MethodHandle findVirtual(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) {
+ try {
+ return explicitLookup.findVirtual(clazz, name, type);
+ } catch (final NoSuchMethodException | IllegalAccessException e) {
+ throw new LookupException(e);
+ }
+ }
+
+ @Override
+ public SwitchPoint createSwitchPoint() {
+ return new SwitchPoint();
+ }
+
+ @Override
+ public MethodHandle guardWithTest(final SwitchPoint sp, final MethodHandle before, final MethodHandle after) {
+ return sp.guardWithTest(before, after);
+ }
+
+ @Override
+ public MethodType type(final Class<?> returnType, final Class<?>... paramTypes) {
+ return MethodType.methodType(returnType, paramTypes);
+ }
+
+ }
+
+ /**
+ * Class used for instrumenting and debugging Nashorn generated method handles
+ */
+ private static class TraceMethodHandleFunctionality extends StandardMethodHandleFunctionality {
+
+ protected static String describe(final Object... data) {
+ final StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < data.length; i++) {
+ final Object d = data[i];
+ if (d == null) {
+ sb.append("<null> ");
+ } else if (d instanceof String || d instanceof ConsString) {
+ sb.append(d.toString());
+ sb.append(' ');
+ } else if (d.getClass().isArray()) {
+ sb.append("[ ");
+ for (final Object da : (Object[])d) {
+ sb.append(describe(new Object[]{ da })).append(' ');
+ }
+ sb.append("] ");
+ } else {
+ sb.append(d)
+ .append('{')
+ .append(Integer.toHexString(System.identityHashCode(d)))
+ .append('}');
+ }
+
+ if (i + 1 < data.length) {
+ sb.append(", ");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public MethodHandle debug(final MethodHandle master, final String str, final Object... args) {
+ return addDebugPrintout(LOG, master, Integer.MAX_VALUE, false, str + ' ' + describe(args));
+ }
+
+ @Override
+ public MethodHandle filterArguments(final MethodHandle target, final int pos, final MethodHandle... filters) {
+ final MethodHandle mh = super.filterArguments(target, pos, filters);
+ return debug(mh, "filterArguments", target, pos, filters);
+ }
+
+ @Override
+ public MethodHandle filterReturnValue(final MethodHandle target, final MethodHandle filter) {
+ final MethodHandle mh = super.filterReturnValue(target, filter);
+ return debug(mh, "filterReturnValue", target, filter);
+ }
+
+ @Override
+ public MethodHandle guardWithTest(final MethodHandle test, final MethodHandle target, final MethodHandle fallback) {
+ final MethodHandle mh = super.guardWithTest(test, target, fallback);
+ return debug(mh, "guardWithTest", test, target, fallback);
+ }
+
+ @Override
+ public MethodHandle insertArguments(final MethodHandle target, final int pos, final Object... values) {
+ final MethodHandle mh = super.insertArguments(target, pos, values);
+ return debug(mh, "insertArguments", target, pos, values);
+ }
+
+ @Override
+ public MethodHandle dropArguments(final MethodHandle target, final int pos, final Class<?>... values) {
+ final MethodHandle mh = super.dropArguments(target, pos, values);
+ return debug(mh, "dropArguments", target, pos, values);
+ }
+
+ @Override
+ public MethodHandle dropArguments(final MethodHandle target, final int pos, final List<Class<?>> values) {
+ final MethodHandle mh = super.dropArguments(target, pos, values);
+ return debug(mh, "dropArguments", target, pos, values);
+ }
+
+ @Override
+ public MethodHandle asType(final MethodHandle handle, final MethodType type) {
+ final MethodHandle mh = super.asType(handle, type);
+ return debug(mh, "asType", handle, type);
+ }
+
+ @Override
+ public MethodHandle bindTo(final MethodHandle handle, final Object x) {
+ final MethodHandle mh = super.bindTo(handle, x);
+ return debug(mh, "bindTo", handle, x);
+ }
+
+ @Override
+ public MethodHandle foldArguments(final MethodHandle target, final MethodHandle combiner) {
+ final MethodHandle mh = super.foldArguments(target, combiner);
+ return debug(mh, "foldArguments", target, combiner);
+ }
+
+ @Override
+ public MethodHandle explicitCastArguments(final MethodHandle target, final MethodType type) {
+ final MethodHandle mh = super.explicitCastArguments(target, type);
+ return debug(mh, "explicitCastArguments", target, type);
+ }
+
+ @Override
+ public MethodHandle arrayElementGetter(final Class<?> type) {
+ final MethodHandle mh = super.arrayElementGetter(type);
+ return debug(mh, "arrayElementGetter", type);
+ }
+
+ @Override
+ public MethodHandle arrayElementSetter(final Class<?> type) {
+ final MethodHandle mh = super.arrayElementSetter(type);
+ return debug(mh, "arrayElementSetter", type);
+ }
+
+ @Override
+ public MethodHandle throwException(final Class<?> returnType, final Class<? extends Throwable> exType) {
+ final MethodHandle mh = super.throwException(returnType, exType);
+ return debug(mh, "throwException", returnType, exType);
+ }
+
+ @Override
+ public MethodHandle constant(final Class<?> type, final Object value) {
+ final MethodHandle mh = super.constant(type, value);
+ return debug(mh, "constant", type, value);
+ }
+
+ @Override
+ public MethodHandle asCollector(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) {
+ final MethodHandle mh = super.asCollector(handle, arrayType, arrayLength);
+ return debug(mh, "asCollector", handle, arrayType, arrayLength);
+ }
+
+ @Override
+ public MethodHandle asSpreader(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) {
+ final MethodHandle mh = super.asCollector(handle, arrayType, arrayLength);
+ return debug(mh, "asSpreader", handle, arrayType, arrayLength);
+ }
+
+ @Override
+ public MethodHandle getter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ final MethodHandle mh = super.getter(explicitLookup, clazz, name, type);
+ return debug(mh, "getter", explicitLookup, clazz, name, type);
+ }
+
+ @Override
+ public MethodHandle staticGetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ final MethodHandle mh = super.staticGetter(explicitLookup, clazz, name, type);
+ return debug(mh, "static getter", explicitLookup, clazz, name, type);
+ }
+
+ @Override
+ public MethodHandle setter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ final MethodHandle mh = super.setter(explicitLookup, clazz, name, type);
+ return debug(mh, "setter", explicitLookup, clazz, name, type);
+ }
+
+ @Override
+ public MethodHandle staticSetter(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final Class<?> type) {
+ final MethodHandle mh = super.staticSetter(explicitLookup, clazz, name, type);
+ return debug(mh, "static setter", explicitLookup, clazz, name, type);
+ }
+
+ @Override
+ public MethodHandle find(final Method method) {
+ final MethodHandle mh = super.find(method);
+ return debug(mh, "find", method);
+ }
+
+ @Override
+ public MethodHandle findStatic(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) {
+ final MethodHandle mh = super.findStatic(explicitLookup, clazz, name, type);
+ return debug(mh, "findStatic", explicitLookup, clazz, name, type);
+ }
+
+ @Override
+ public MethodHandle findVirtual(final MethodHandles.Lookup explicitLookup, final Class<?> clazz, final String name, final MethodType type) {
+ final MethodHandle mh = super.findVirtual(explicitLookup, clazz, name, type);
+ return debug(mh, "findVirtual", explicitLookup, clazz, name, type);
+ }
+
+ @Override
+ public SwitchPoint createSwitchPoint() {
+ final SwitchPoint sp = super.createSwitchPoint();
+ LOG.log("createSwitchPoint " + sp, TRACE_LEVEL);
+ return sp;
+ }
+
+ @Override
+ public MethodHandle guardWithTest(final SwitchPoint sp, final MethodHandle before, final MethodHandle after) {
+ final MethodHandle mh = super.guardWithTest(sp, before, after);
+ return debug(mh, "guardWithTest", sp, before, after);
+ }
+
+ @Override
+ public MethodType type(final Class<?> returnType, final Class<?>... paramTypes) {
+ final MethodType mt = super.type(returnType, paramTypes);
+ LOG.log("methodType " + returnType + ' ' + Arrays.toString(paramTypes) + ' ' + mt, TRACE_LEVEL);
+ return mt;
+ }
+ }
+
+ /**
+ * Class used for debugging Nashorn generated method handles
+ */
+ private static class TraceCreateMethodHandleFunctionality extends TraceMethodHandleFunctionality {
+ @Override
+ public MethodHandle debug(final MethodHandle master, final String str, final Object... args) {
+ LOG.log(str + ' ' + describe(args), TRACE_LEVEL);
+ stacktrace(LOG);
+ return master;
+ }
+ }
+}