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 
  26 package jdk.nashorn.internal.objects;
  27 
  28 import static jdk.nashorn.internal.lookup.Lookup.MH;
  29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  31 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
  32 
  33 import java.lang.invoke.MethodHandle;
  34 import java.lang.invoke.MethodHandles;
  35 import java.lang.invoke.MethodType;
  36 import java.util.ArrayList;
  37 import java.util.Iterator;
  38 import java.util.List;
  39 import jdk.dynalink.CallSiteDescriptor;
  40 import jdk.dynalink.linker.GuardedInvocation;
  41 import jdk.dynalink.linker.LinkRequest;
  42 import jdk.nashorn.internal.lookup.Lookup;
  43 import jdk.nashorn.internal.objects.annotations.Constructor;
  44 import jdk.nashorn.internal.objects.annotations.ScriptClass;
  45 import jdk.nashorn.internal.runtime.FindProperty;
  46 import jdk.nashorn.internal.runtime.JSType;
  47 import jdk.nashorn.internal.runtime.PropertyMap;
  48 import jdk.nashorn.internal.runtime.ScriptFunction;
  49 import jdk.nashorn.internal.runtime.ScriptObject;
  50 import jdk.nashorn.internal.runtime.ScriptRuntime;
  51 import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
  52 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  53 import 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")
 112 public 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 }