NativeJSAdapter.java revision 1805:7caf1f762f1d
1/*
2 * Copyright (c) 2010, 2015, 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.objects;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
31import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
32
33import java.lang.invoke.MethodHandle;
34import java.lang.invoke.MethodHandles;
35import java.lang.invoke.MethodType;
36import java.util.ArrayList;
37import java.util.Iterator;
38import java.util.List;
39import jdk.dynalink.CallSiteDescriptor;
40import jdk.dynalink.linker.GuardedInvocation;
41import jdk.dynalink.linker.LinkRequest;
42import jdk.nashorn.internal.lookup.Lookup;
43import jdk.nashorn.internal.objects.annotations.Constructor;
44import jdk.nashorn.internal.objects.annotations.ScriptClass;
45import jdk.nashorn.internal.runtime.FindProperty;
46import jdk.nashorn.internal.runtime.JSType;
47import jdk.nashorn.internal.runtime.PropertyMap;
48import jdk.nashorn.internal.runtime.ScriptFunction;
49import jdk.nashorn.internal.runtime.ScriptObject;
50import jdk.nashorn.internal.runtime.ScriptRuntime;
51import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
52import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
53import jdk.nashorn.internal.scripts.JO;
54
55/**
56 * This class is the implementation of the Nashorn-specific global object named {@code JSAdapter}. It can be thought of
57 * as the {@link java.lang.reflect.Proxy} equivalent for JavaScript. A {@code NativeJSAdapter} calls specially named
58 * JavaScript methods on an adaptee object when property access/update/call/new/delete is attempted on it. Example:
59 *<pre>
60 *    var y = {
61 *                __get__     : function (name) { ... }
62 *                __has__     : function (name) { ... }
63 *                __put__     : function (name, value) {...}
64 *                __call__    : function (name, arg1, arg2) {...}
65 *                __new__     : function (arg1, arg2) {...}
66 *                __delete__  : function (name) { ... }
67 *                __getKeys__ : function () { ... }
68 *            };
69 *
70 *    var x = new JSAdapter(y);
71 *
72 *    x.i;                        // calls y.__get__
73 *    x.foo();                    // calls y.__call__
74 *    new x();                    // calls y.__new__
75 *    i in x;                     // calls y.__has__
76 *    x.p = 10;                   // calls y.__put__
77 *    delete x.p;                 // calls y.__delete__
78 *    for (i in x) { print(i); }  // calls y.__getKeys__
79 * </pre>
80 * <p>
81 * The {@code __getKeys__} and {@code __getIds__} properties are mapped to the same operation. Concrete
82 * {@code JSAdapter} implementations are expected to use only one of these. As {@code __getIds__} exists for
83 * compatibility reasons only, use of {@code __getKeys__} is recommended.
84 * </p>
85 * <p>
86 * The JavaScript caller of an adapter object is oblivious of the property access/mutation/deletion's being adapted.
87 * </p>
88 * <p>
89 * The {@code JSAdapter} constructor can optionally receive an "overrides" object. The properties of overrides object
90 * are copied to the {@code JSAdapter} instance. In case user-accessed properties are among these, the adaptee's methods
91 * like {@code __get__}, {@code __put__} etc. are not called for them. This can be used to make certain "preferred"
92 * properties that can be accessed in the usual/faster way avoiding the proxy mechanism. Example:
93 * </p>
94 * <pre>
95 *     var x = new JSAdapter({ foo: 444, bar: 6546 }) {
96 *          __get__: function(name) { return name; }
97 *      };
98 *
99 *     x.foo;           // 444 directly retrieved without __get__ call
100 *     x.bar = 'hello'; // "bar" directly set without __put__ call
101 *     x.prop           // calls __get__("prop") as 'prop' is not overridden
102 * </pre>
103 * It is possible to pass a specific prototype for the {@code JSAdapter} instance by passing three arguments to the
104 * {@code JSAdapter} constructor. The exact signature of the {@code JSAdapter} constructor is as follows:
105 * <pre>
106 *     JSAdapter([proto], [overrides], adaptee);
107 * </pre>
108 * Both the {@code proto} and {@code overrides} arguments are optional - but {@code adaptee} is not. When {@code proto}
109 * is not passed, {@code JSAdapter.prototype} is used.
110 */
111@ScriptClass("JSAdapter")
112public final class NativeJSAdapter extends ScriptObject {
113    /** object get operation */
114    public static final String __get__       = "__get__";
115    /** object out operation */
116    public static final String __put__       = "__put__";
117    /** object call operation */
118    public static final String __call__      = "__call__";
119    /** object new operation */
120    public static final String __new__       = "__new__";
121    /** object getIds operation (provided for compatibility reasons; use of getKeys is preferred) */
122    public static final String __getIds__    = "__getIds__";
123    /** object getKeys operation */
124    public static final String __getKeys__   = "__getKeys__";
125    /** object getValues operation */
126    public static final String __getValues__ = "__getValues__";
127    /** object has operation */
128    public static final String __has__       = "__has__";
129    /** object delete operation */
130    public static final String __delete__    = "__delete__";
131
132    // the new extensibility, sealing and freezing operations
133
134    /** prevent extensions operation */
135    public static final String __preventExtensions__ = "__preventExtensions__";
136    /** isExtensible extensions operation */
137    public static final String __isExtensible__      = "__isExtensible__";
138    /** seal operation */
139    public static final String __seal__              = "__seal__";
140    /** isSealed extensions operation */
141    public static final String __isSealed__          = "__isSealed__";
142    /** freeze operation */
143    public static final String __freeze__            = "__freeze__";
144    /** isFrozen extensions operation */
145    public static final String __isFrozen__          = "__isFrozen__";
146
147    private final ScriptObject adaptee;
148    private final boolean overrides;
149
150    private static final MethodHandle IS_JSADAPTER = findOwnMH("isJSAdapter", boolean.class, Object.class, Object.class, MethodHandle.class, Object.class, ScriptFunction.class);
151
152    // initialized by nasgen
153    private static PropertyMap $nasgenmap$;
154
155    NativeJSAdapter(final Object overrides, final ScriptObject adaptee, final ScriptObject proto, final PropertyMap map) {
156        super(proto, map);
157        this.adaptee = wrapAdaptee(adaptee);
158        if (overrides instanceof ScriptObject) {
159            this.overrides = true;
160            final ScriptObject sobj = (ScriptObject)overrides;
161            this.addBoundProperties(sobj);
162        } else {
163            this.overrides = false;
164        }
165    }
166
167    private static ScriptObject wrapAdaptee(final ScriptObject adaptee) {
168        return new JO(adaptee);
169    }
170
171    @Override
172    public String getClassName() {
173        return "JSAdapter";
174    }
175
176    @Override
177    public int getInt(final Object key, final int programPoint) {
178        return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key);
179    }
180
181    @Override
182    public int getInt(final double key, final int programPoint) {
183        return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key);
184    }
185
186    @Override
187    public int getInt(final int key, final int programPoint) {
188        return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key);
189    }
190
191    @Override
192    public double getDouble(final Object key, final int programPoint) {
193        return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key);
194    }
195
196    @Override
197    public double getDouble(final double key, final int programPoint) {
198        return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key);
199    }
200
201    @Override
202    public double getDouble(final int key, final int programPoint) {
203        return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key);
204    }
205
206    @Override
207    public Object get(final Object key) {
208        return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key);
209    }
210
211    @Override
212    public Object get(final double key) {
213        return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key);
214    }
215
216    @Override
217    public Object get(final int key) {
218        return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key);
219    }
220
221    @Override
222    public void set(final Object key, final int value, final int flags) {
223        if (overrides && super.hasOwnProperty(key)) {
224            super.set(key, value, flags);
225        } else {
226            callAdaptee(__put__, key, value, flags);
227        }
228    }
229
230    @Override
231    public void set(final Object key, final double value, final int flags) {
232        if (overrides && super.hasOwnProperty(key)) {
233            super.set(key, value, flags);
234        } else {
235            callAdaptee(__put__, key, value, flags);
236        }
237    }
238
239    @Override
240    public void set(final Object key, final Object value, final int flags) {
241        if (overrides && super.hasOwnProperty(key)) {
242            super.set(key, value, flags);
243        } else {
244            callAdaptee(__put__, key, value, flags);
245        }
246    }
247
248    @Override
249    public void set(final double key, final int value, final int flags) {
250        if (overrides && super.hasOwnProperty(key)) {
251            super.set(key, value, flags);
252        } else {
253            callAdaptee(__put__, key, value, flags);
254        }
255    }
256
257    @Override
258    public void set(final double key, final double value, final int flags) {
259        if (overrides && super.hasOwnProperty(key)) {
260            super.set(key, value, flags);
261        } else {
262            callAdaptee(__put__, key, value, flags);
263        }
264    }
265
266    @Override
267    public void set(final double key, final Object value, final int flags) {
268        if (overrides && super.hasOwnProperty(key)) {
269            super.set(key, value, flags);
270        } else {
271            callAdaptee(__put__, key, value, flags);
272        }
273    }
274
275    @Override
276    public void set(final int key, final int value, final int flags) {
277        if (overrides && super.hasOwnProperty(key)) {
278            super.set(key, value, flags);
279        } else {
280            callAdaptee(__put__, key, value, flags);
281        }
282    }
283
284    @Override
285    public void set(final int key, final double value, final int flags) {
286        if (overrides && super.hasOwnProperty(key)) {
287            super.set(key, value, flags);
288        } else {
289            callAdaptee(__put__, key, value, flags);
290        }
291    }
292
293    @Override
294    public void set(final int key, final Object value, final int flags) {
295        if (overrides && super.hasOwnProperty(key)) {
296            super.set(key, value, flags);
297        } else {
298            callAdaptee(__put__, key, value, flags);
299        }
300    }
301
302    @Override
303    public boolean has(final Object key) {
304        if (overrides && super.hasOwnProperty(key)) {
305            return true;
306        }
307
308        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key));
309    }
310
311    @Override
312    public boolean has(final int key) {
313        if (overrides && super.hasOwnProperty(key)) {
314            return true;
315        }
316
317        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key));
318    }
319
320    @Override
321    public boolean has(final double key) {
322        if (overrides && super.hasOwnProperty(key)) {
323            return true;
324        }
325
326        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key));
327    }
328
329    @Override
330    public boolean delete(final int key, final boolean strict) {
331        if (overrides && super.hasOwnProperty(key)) {
332            return super.delete(key, strict);
333        }
334
335        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict));
336    }
337
338    @Override
339    public boolean delete(final double key, final boolean strict) {
340        if (overrides && super.hasOwnProperty(key)) {
341            return super.delete(key, strict);
342        }
343
344        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict));
345    }
346
347    @Override
348    public boolean delete(final Object key, final boolean strict) {
349        if (overrides && super.hasOwnProperty(key)) {
350            return super.delete(key, strict);
351        }
352
353        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict));
354    }
355
356    @Override
357    public Iterator<String> propertyIterator() {
358        // Try __getIds__ first, if not found then try __getKeys__
359        // In jdk6, we had added "__getIds__" so this is just for compatibility.
360        Object func = adaptee.get(__getIds__);
361        if (!(func instanceof ScriptFunction)) {
362            func = adaptee.get(__getKeys__);
363        }
364
365        Object obj;
366        if (func instanceof ScriptFunction) {
367            obj = ScriptRuntime.apply((ScriptFunction)func, this);
368        } else {
369            obj = new NativeArray(0);
370        }
371
372        final List<String> array = new ArrayList<>();
373        for (final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(obj); iter.hasNext(); ) {
374            array.add((String)iter.next());
375        }
376
377        return array.iterator();
378    }
379
380
381    @Override
382    public Iterator<Object> valueIterator() {
383        final Object obj = callAdaptee(new NativeArray(0), __getValues__);
384        return ArrayLikeIterator.arrayLikeIterator(obj);
385    }
386
387    @Override
388    public ScriptObject preventExtensions() {
389        callAdaptee(__preventExtensions__);
390        return this;
391    }
392
393    @Override
394    public boolean isExtensible() {
395        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __isExtensible__));
396    }
397
398    @Override
399    public ScriptObject seal() {
400        callAdaptee(__seal__);
401        return this;
402    }
403
404    @Override
405    public boolean isSealed() {
406        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isSealed__));
407    }
408
409    @Override
410    public ScriptObject freeze() {
411        callAdaptee(__freeze__);
412        return this;
413    }
414
415    @Override
416    public boolean isFrozen() {
417        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isFrozen__));
418    }
419
420    /**
421     * Constructor
422     *
423     * @param isNew is this NativeJSAdapter instantiated with the new operator
424     * @param self  self reference
425     * @param args  arguments ([adaptee], [overrides, adaptee] or [proto, overrides, adaptee]
426     * @return new NativeJSAdapter
427     */
428    @Constructor
429    public static NativeJSAdapter construct(final boolean isNew, final Object self, final Object... args) {
430        Object proto     = UNDEFINED;
431        Object overrides = UNDEFINED;
432        Object adaptee;
433
434        if (args == null || args.length == 0) {
435            throw typeError("not.an.object", "null");
436        }
437
438        switch (args.length) {
439        case 1:
440            adaptee = args[0];
441            break;
442
443        case 2:
444            overrides = args[0];
445            adaptee   = args[1];
446            break;
447
448        default:
449            //fallthru
450        case 3:
451            proto = args[0];
452            overrides = args[1];
453            adaptee = args[2];
454            break;
455        }
456
457        if (!(adaptee instanceof ScriptObject)) {
458            throw typeError("not.an.object", ScriptRuntime.safeToString(adaptee));
459        }
460
461        final Global global = Global.instance();
462        if (proto != null && !(proto instanceof ScriptObject)) {
463            proto = global.getJSAdapterPrototype();
464        }
465
466        return new NativeJSAdapter(overrides, (ScriptObject)adaptee, (ScriptObject)proto, $nasgenmap$);
467    }
468
469    @Override
470    protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) {
471        return findHook(desc, __new__, false);
472    }
473
474    @Override
475    protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request) {
476        final String name = NashornCallSiteDescriptor.getOperand(desc);
477        if (overrides && super.hasOwnProperty(name)) {
478            try {
479                final GuardedInvocation inv = super.findGetMethod(desc, request);
480                if (inv != null) {
481                    return inv;
482                }
483            } catch (final Exception e) {
484                //ignored
485            }
486        }
487
488        if (!NashornCallSiteDescriptor.isMethodFirstOperation(desc)) {
489            return findHook(desc, __get__);
490        } else {
491            final FindProperty find = adaptee.findProperty(__call__, true);
492            if (find != null) {
493                final Object value = find.getObjectValue();
494                if (value instanceof ScriptFunction) {
495                    final ScriptFunction func = (ScriptFunction)value;
496                    // TODO: It's a shame we need to produce a function bound to this and name, when we'd only need it bound
497                    // to name. Probably not a big deal, but if we can ever make it leaner, it'd be nice.
498                    return new GuardedInvocation(MH.dropArguments(MH.constant(Object.class,
499                            func.createBound(this, new Object[] { name })), 0, Object.class),
500                            testJSAdapter(adaptee, null, null, null),
501                            adaptee.getProtoSwitchPoints(__call__, find.getOwner()), null);
502                }
503            }
504            throw typeError("no.such.function", name, ScriptRuntime.safeToString(this));
505        }
506    }
507
508    @Override
509    protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) {
510        if (overrides && super.hasOwnProperty(NashornCallSiteDescriptor.getOperand(desc))) {
511            try {
512                final GuardedInvocation inv = super.findSetMethod(desc, request);
513                if (inv != null) {
514                    return inv;
515                }
516            } catch (final Exception e) {
517                //ignored
518            }
519        }
520
521        return findHook(desc, __put__);
522    }
523
524    // -- Internals only below this point
525    private Object callAdaptee(final String name, final Object... args) {
526        return callAdaptee(UNDEFINED, name, args);
527    }
528
529    private double callAdapteeDouble(final int programPoint, final String name, final Object... args) {
530        return JSType.toNumberMaybeOptimistic(callAdaptee(name, args), programPoint);
531    }
532
533    private int callAdapteeInt(final int programPoint, final String name, final Object... args) {
534        return JSType.toInt32MaybeOptimistic(callAdaptee(name, args), programPoint);
535    }
536
537    private Object callAdaptee(final Object retValue, final String name, final Object... args) {
538        final Object func = adaptee.get(name);
539        if (func instanceof ScriptFunction) {
540            return ScriptRuntime.apply((ScriptFunction)func, this, args);
541        }
542        return retValue;
543    }
544
545    private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook) {
546        return findHook(desc, hook, true);
547    }
548
549    private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook, final boolean useName) {
550        final FindProperty findData = adaptee.findProperty(hook, true);
551        final MethodType type = desc.getMethodType();
552        if (findData != null) {
553            final String name = NashornCallSiteDescriptor.getOperand(desc);
554            final Object value = findData.getObjectValue();
555            if (value instanceof ScriptFunction) {
556                final ScriptFunction func = (ScriptFunction)value;
557
558                final MethodHandle methodHandle = getCallMethodHandle(findData, type,
559                    useName ? name : null);
560                if (methodHandle != null) {
561                    return new GuardedInvocation(
562                            methodHandle,
563                            testJSAdapter(adaptee, findData.getGetter(Object.class, INVALID_PROGRAM_POINT, null), findData.getOwner(), func),
564                            adaptee.getProtoSwitchPoints(hook, findData.getOwner()), null);
565                }
566             }
567        }
568
569        switch (hook) {
570        case __call__:
571            throw typeError("no.such.function", NashornCallSiteDescriptor.getOperand(desc), ScriptRuntime.safeToString(this));
572        default:
573            final MethodHandle methodHandle = hook.equals(__put__) ?
574            MH.asType(Lookup.EMPTY_SETTER, type) :
575            Lookup.emptyGetter(type.returnType());
576            return new GuardedInvocation(methodHandle, testJSAdapter(adaptee, null, null, null), adaptee.getProtoSwitchPoints(hook, null), null);
577        }
578    }
579
580    private static MethodHandle testJSAdapter(final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) {
581        return MH.insertArguments(IS_JSADAPTER, 1, adaptee, getter, where, func);
582    }
583
584    @SuppressWarnings("unused")
585    private static boolean isJSAdapter(final Object self, final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) {
586        final boolean res = self instanceof NativeJSAdapter && ((NativeJSAdapter)self).getAdaptee() == adaptee;
587        if (res && getter != null) {
588            try {
589                return getter.invokeExact(where) == func;
590            } catch (final RuntimeException | Error e) {
591                throw e;
592            } catch (final Throwable t) {
593                throw new RuntimeException(t);
594            }
595        }
596
597        return res;
598    }
599
600    /**
601     * Get the adaptee
602     * @return adaptee ScriptObject
603     */
604    public ScriptObject getAdaptee() {
605        return adaptee;
606    }
607
608    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
609        return MH.findStatic(MethodHandles.lookup(), NativeJSAdapter.class, name, MH.type(rtype, types));
610    }
611}
612