aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/linker/Lookup.java
blob: 6a95a3c4422c0dc06198298baf525b4817943d14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/*
 * 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.runtime.linker;

import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.Property;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.ScriptRuntime;

/**
 * MethodHandle Lookup management for Nashorn.
 */
public final class Lookup {

    /**
     * A global singleton that points to the {@link MethodHandleFunctionality}. This is basically
     * a collection of wrappers to the standard methods in {@link MethodHandle}, {@link MethodHandles} and
     * {@link java.lang.invoke.MethodHandles.Lookup}, but instrumentation and debugging purposes we need
     * intercept points.
     * <p>
     * All method handle operations in Nashorn should go through this field, not directly to the classes
     * in {@code java.lang.invoke}
     */
    public static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();

    /** Method handle to the empty getter */
    public static final MethodHandle EMPTY_GETTER = findOwnMH("emptyGetter", Object.class, Object.class);

    /** Method handle to the empty setter */
    public static final MethodHandle EMPTY_SETTER = findOwnMH("emptySetter", void.class, Object.class, Object.class);

    /** Method handle to a getter that only throws type error */
    public static final MethodHandle TYPE_ERROR_THROWER_GETTER = findOwnMH("typeErrorThrowerGetter", Object.class, Object.class);

    /** Method handle to a setter that only throws type error */
    public static final MethodHandle TYPE_ERROR_THROWER_SETTER = findOwnMH("typeErrorThrowerSetter", void.class, Object.class, Object.class);

    /** Method handle to the most generic of getters, the one that returns an Object */
    public static final MethodType GET_OBJECT_TYPE = MH.type(Object.class, Object.class);

    /** Method handle to the most generic of setters, the one that takes an Object */
    public static final MethodType SET_OBJECT_TYPE = MH.type(void.class, Object.class, Object.class);

    private Lookup() {
    }

    /**
     * Empty getter implementation. Nop
     * @param self self reference
     * @return undefined
     */
    public static Object emptyGetter(final Object self) {
        return UNDEFINED;
    }

    /**
     * Empty setter implementation. Nop
     * @param self  self reference
     * @param value value (ignored)
     */
    public static void emptySetter(final Object self, final Object value) {
        // do nothing!!
    }

    /**
     * Return a method handle to the empty getter, with a different
     * return type value. It will still be undefined cast to whatever
     * return value property was specified
     *
     * @param type return value type
     *
     * @return undefined as return value type
     */
    public static MethodHandle emptyGetter(final Class<?> type) {
        return filterReturnType(EMPTY_GETTER, type);
    }

    /**
     * Getter function that always throws type error
     *
     * @param self  self reference
     * @return undefined (but throws error before return point)
     */
    public static Object typeErrorThrowerGetter(final Object self) {
        throw typeError("strict.getter.setter.poison", ScriptRuntime.safeToString(self));
    }

    /**
     * Getter function that always throws type error
     *
     * @param self  self reference
     * @param value (ignored)
     */
    public static void typeErrorThrowerSetter(final Object self, final Object value) {
        throw typeError("strict.getter.setter.poison", ScriptRuntime.safeToString(self));
    }

    /**
     * Create a new {@link Property}
     *
     * @param map             property map
     * @param key             property key
     * @param flags           property flags
     * @param propertyGetter  getter for property if available, null otherwise
     * @param propertySetter  setter for property if available, null otherwise
     *
     * @return new property map, representing {@code PropertyMap} with the new property added to it
     */
    @SuppressWarnings("fallthrough")
    public static PropertyMap newProperty(final PropertyMap map, final String key, final int flags, final MethodHandle propertyGetter, final MethodHandle propertySetter) {
        MethodHandle getter = propertyGetter;
        MethodHandle setter = propertySetter;

        // TODO: this is temporary code. This code exists to support reflective
        // field reader/writer handles generated by "unreflect" lookup.

        switch (getter.type().parameterCount()) {
        case 0:
            // A static field reader, so drop the 'self' argument.
            getter = MH.dropArguments(getter, 0, Object.class);
            if (setter != null) {
                setter = MH.dropArguments(setter, 0, Object.class);
            }
        // fall through
        case 1:
            // standard getter that accepts 'self'.
            break;
        default:
            // Huh!! something wrong..
            throw new IllegalArgumentException("getter/setter has wrong arguments");
        }

        return map.newProperty(key, flags, -1, getter, setter);
    }

    /**
     * This method filters primitive return types using JavaScript semantics. For example,
     * an (int) cast of a double in Java land is not the same thing as invoking toInt32 on it.
     * If you are returning values to JavaScript that have to be of a specific type, this is
     * the correct return value filter to use, as the explicitCastArguments just uses the
     * Java boxing equivalents
     *
     * @param mh   method handle for which to filter return value
     * @param type new return type
     * @return method handle for appropriate return type conversion
     */
    public static MethodHandle filterReturnType(final MethodHandle mh, final Class<?> type) {
        final Class<?> retType = mh.type().returnType();

        if (retType == int.class) {
            //fallthru
        } else if (retType == long.class) {
            //fallthru
        } else if (retType == double.class) {
            if (type == int.class) {
                return MH.filterReturnValue(mh, JSType.TO_INT32_D.methodHandle());
            } else if (type == long.class) {
                return MH.filterReturnValue(mh, JSType.TO_UINT32_D.methodHandle());
            }
            //fallthru
        } else if (!retType.isPrimitive()) {
            if (type == int.class) {
                return MH.filterReturnValue(mh, JSType.TO_INT32.methodHandle());
            } else if (type == long.class) {
                return MH.filterReturnValue(mh, JSType.TO_UINT32.methodHandle());
            } else if (type == double.class) {
                return MH.filterReturnValue(mh, JSType.TO_NUMBER.methodHandle());
            } else if (!type.isPrimitive()) {
                return mh;
            }

            assert false : "unsupported Lookup.filterReturnType type " + retType + " -> " + type;
        }

        //use a standard cast - we don't need to check JavaScript special cases
        return MH.explicitCastArguments(mh, mh.type().changeReturnType(type));
    }

    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
        return MH.findStatic(MethodHandles.lookup(), Lookup.class, name, MH.type(rtype, types));
    }

}