UserAccessorProperty.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.runtime;
27
28import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
29import static jdk.nashorn.internal.lookup.Lookup.MH;
30import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
31import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT_OPTIMISTIC;
32import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex;
33import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
34
35import java.lang.invoke.MethodHandle;
36import java.lang.invoke.MethodHandles;
37import java.util.concurrent.Callable;
38import jdk.nashorn.internal.codegen.CompilerConstants;
39import jdk.nashorn.internal.lookup.Lookup;
40import jdk.nashorn.internal.runtime.linker.Bootstrap;
41
42/**
43 * Property with user defined getters/setters. Actual getter and setter
44 * functions are stored in underlying ScriptObject. Only the 'slot' info is
45 * stored in the property.
46 *
47 * The slots here denote either ScriptObject embed field number or spill
48 * array index. For spill array index, we use slot value of
49 * (index + ScriptObject.embedSize). See also ScriptObject.getEmbedOrSpill
50 * method. Negative slot value means that the corresponding getter or setter
51 * is null. Note that always two slots are allocated in ScriptObject - but
52 * negative (less by 1) slot number is stored for null getter or setter.
53 * This is done so that when the property is redefined with a different
54 * getter and setter (say, both non-null), we'll have spill slots to store
55 * those. When a slot is negative, (-slot - 1) is the embed/spill index.
56 */
57public final class UserAccessorProperty extends SpillProperty {
58
59    private static final long serialVersionUID = -5928687246526840321L;
60
61    static class Accessors {
62        Object getter;
63        Object setter;
64
65        Accessors(final Object getter, final Object setter) {
66            set(getter, setter);
67        }
68
69        final void set(final Object getter, final Object setter) {
70            this.getter = getter;
71            this.setter = setter;
72        }
73
74        @Override
75        public String toString() {
76            return "[getter=" + getter + " setter=" + setter + ']';
77        }
78    }
79
80    /** Getter method handle */
81    private final static CompilerConstants.Call USER_ACCESSOR_GETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class,
82            "userAccessorGetter", Object.class, Accessors.class, Object.class);
83
84    /** Setter method handle */
85    private final static CompilerConstants.Call USER_ACCESSOR_SETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class,
86            "userAccessorSetter", void.class, Accessors.class, String.class, Object.class, Object.class);
87
88    /** Dynamic invoker for getter */
89    private static final Object INVOKE_UA_GETTER = new Object();
90
91    private static MethodHandle getINVOKE_UA_GETTER() {
92
93        return Context.getGlobal().getDynamicInvoker(INVOKE_UA_GETTER,
94                new Callable<MethodHandle>() {
95                    @Override
96                    public MethodHandle call() {
97                        return Bootstrap.createDynamicInvoker("dyn:call", Object.class,
98                            Object.class, Object.class);
99                    }
100                });
101    }
102
103    /** Dynamic invoker for setter */
104    private static Object INVOKE_UA_SETTER = new Object();
105
106    private static MethodHandle getINVOKE_UA_SETTER() {
107        return Context.getGlobal().getDynamicInvoker(INVOKE_UA_SETTER,
108                new Callable<MethodHandle>() {
109                    @Override
110                    public MethodHandle call() {
111                        return Bootstrap.createDynamicInvoker("dyn:call", void.class,
112                            Object.class, Object.class, Object.class);
113                    }
114                });
115    }
116
117    /**
118     * Constructor
119     *
120     * @param key        property key
121     * @param flags      property flags
122     * @param getterSlot getter slot, starting at first embed
123     * @param setterSlot setter slot, starting at first embed
124     */
125    UserAccessorProperty(final String key, final int flags, final int slot) {
126        super(key, flags, slot);
127    }
128
129    private UserAccessorProperty(final UserAccessorProperty property) {
130        super(property);
131    }
132
133    private UserAccessorProperty(final UserAccessorProperty property, final Class<?> newType) {
134        super(property, newType);
135    }
136
137    @Override
138    public Property copy() {
139        return new UserAccessorProperty(this);
140    }
141
142    @Override
143    public Property copy(final Class<?> newType) {
144        return new UserAccessorProperty(this, newType);
145    }
146
147    void setAccessors(final ScriptObject sobj, final PropertyMap map, final Accessors gs) {
148        try {
149            //invoke the getter and find out
150            super.getSetter(Object.class, map).invokeExact((Object)sobj, (Object)gs);
151        } catch (final Error | RuntimeException t) {
152            throw t;
153        } catch (final Throwable t) {
154            throw new RuntimeException(t);
155        }
156    }
157
158    //pick the getter setter out of the correct spill slot in sobj
159    Accessors getAccessors(final ScriptObject sobj) {
160        try {
161            //invoke the super getter with this spill slot
162            //get the getter setter from the correct spill slot
163            final Object gs = super.getGetter(Object.class).invokeExact((Object)sobj);
164            return (Accessors)gs;
165        } catch (final Error | RuntimeException t) {
166            throw t;
167        } catch (final Throwable t) {
168            throw new RuntimeException(t);
169        }
170    }
171
172    @Override
173    public Class<?> getCurrentType() {
174        return Object.class;
175    }
176
177    @Override
178    public boolean hasGetterFunction(final ScriptObject sobj) {
179        return getAccessors(sobj).getter != null;
180    }
181
182    @Override
183    public boolean hasSetterFunction(final ScriptObject sobj) {
184        return getAccessors(sobj).setter != null;
185    }
186
187    @Override
188    public int getIntValue(final ScriptObject self, final ScriptObject owner) {
189        return (int)getObjectValue(self, owner);
190    }
191
192    @Override
193    public long getLongValue(final ScriptObject self, final ScriptObject owner) {
194        return (long)getObjectValue(self, owner);
195    }
196
197    @Override
198    public double getDoubleValue(final ScriptObject self, final ScriptObject owner) {
199        return (double)getObjectValue(self, owner);
200    }
201
202    @Override
203    public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
204        return userAccessorGetter(getAccessors((owner != null) ? owner : self), self);
205    }
206
207    @Override
208    public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict) {
209        setValue(self, owner, value, strict);
210    }
211
212    @Override
213    public void setValue(final ScriptObject self, final ScriptObject owner, final long value, final boolean strict) {
214        setValue(self, owner, value, strict);
215    }
216
217    @Override
218    public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict) {
219        setValue(self, owner, value, strict);
220    }
221
222    @Override
223    public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) {
224        userAccessorSetter(getAccessors((owner != null) ? owner : self), strict ? getKey() : null, self, value);
225    }
226
227    @Override
228    public MethodHandle getGetter(final Class<?> type) {
229        //this returns a getter on the format (Accessors, Object receiver)
230        return Lookup.filterReturnType(USER_ACCESSOR_GETTER.methodHandle(), type);
231    }
232
233    @Override
234    public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) {
235        //fortype is always object, but in the optimistic world we have to throw
236        //unwarranted optimism exception for narrower types. We can improve this
237        //by checking for boxed types and unboxing them, but it is doubtful that
238        //this gives us any performance, as UserAccessorProperties are typically not
239        //primitives. Are there? TODO: investigate later. For now we just throw an
240        //exception for narrower types than object
241
242        if (type.isPrimitive()) {
243            final MethodHandle getter = getGetter(Object.class);
244            final MethodHandle mh =
245                    MH.asType(
246                            MH.filterReturnValue(
247                                    getter,
248                                    MH.insertArguments(
249                                            CONVERT_OBJECT_OPTIMISTIC.get(getAccessorTypeIndex(type)),
250                                            1,
251                                            programPoint)),
252                                    getter.type().changeReturnType(type));
253
254            return mh;
255        }
256
257        assert type == Object.class;
258        return getGetter(type);
259    }
260
261    @Override
262    void initMethodHandles(final Class<?> structure) {
263        throw new UnsupportedOperationException();
264    }
265
266    @Override
267    public ScriptFunction getGetterFunction(final ScriptObject sobj) {
268        final Object value = getAccessors(sobj).getter;
269        return (value instanceof ScriptFunction) ? (ScriptFunction)value : null;
270    }
271
272    @Override
273    public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
274        return USER_ACCESSOR_SETTER.methodHandle();
275    }
276
277    @Override
278    public ScriptFunction getSetterFunction(final ScriptObject sobj) {
279        final Object value = getAccessors(sobj).setter;
280        return (value instanceof ScriptFunction) ? (ScriptFunction)value : null;
281    }
282
283    // User defined getter and setter are always called by "dyn:call". Note that the user
284    // getter/setter may be inherited. If so, proto is bound during lookup. In either
285    // inherited or self case, slot is also bound during lookup. Actual ScriptFunction
286    // to be called is retrieved everytime and applied.
287    static Object userAccessorGetter(final Accessors gs, final Object self) {
288        final Object func = gs.getter;
289        if (func instanceof ScriptFunction) {
290            try {
291                return getINVOKE_UA_GETTER().invokeExact(func, self);
292            } catch (final Error | RuntimeException t) {
293                throw t;
294            } catch (final Throwable t) {
295                throw new RuntimeException(t);
296            }
297        }
298
299        return UNDEFINED;
300    }
301
302    static void userAccessorSetter(final Accessors gs, final String name, final Object self, final Object value) {
303        final Object func = gs.setter;
304        if (func instanceof ScriptFunction) {
305            try {
306                getINVOKE_UA_SETTER().invokeExact(func, self, value);
307            } catch (final Error | RuntimeException t) {
308                throw t;
309            } catch (final Throwable t) {
310                throw new RuntimeException(t);
311            }
312        } else if (name != null) {
313            throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self));
314        }
315    }
316
317}
318