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 
  26 package jdk.nashorn.api.scripting;
  27 
  28 import java.security.AccessControlContext;
  29 import java.security.AccessController;
  30 import java.security.Permissions;
  31 import java.security.PrivilegedAction;
  32 import java.security.ProtectionDomain;
  33 import java.util.AbstractMap;
  34 import java.util.ArrayList;
  35 import java.util.Collection;
  36 import java.util.Collections;
  37 import java.util.Iterator;
  38 import java.util.LinkedHashSet;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.util.Set;
  42 import java.util.concurrent.Callable;
  43 import javax.script.Bindings;
  44 import jdk.nashorn.internal.runtime.Context;
  45 import jdk.nashorn.internal.runtime.GlobalObject;
  46 import jdk.nashorn.internal.runtime.ScriptFunction;
  47 import jdk.nashorn.internal.runtime.ScriptObject;
  48 import jdk.nashorn.internal.runtime.ScriptRuntime;
  49 
  50 /**
  51  * Mirror object that wraps a given ScriptObject instance. User can
  52  * access ScriptObject via the javax.script.Bindings interface or
  53  * netscape.javascript.JSObject interface.
  54  */
  55 public final class ScriptObjectMirror extends JSObject implements Bindings {
  56     private static AccessControlContext getContextAccCtxt() {
  57         final Permissions perms = new Permissions();
  58         perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
  59         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
  60     }
  61 
  62     private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
  63 
  64     private final ScriptObject sobj;
  65     private final ScriptObject global;
  66     private final boolean strict;
  67 
  68     @Override
  69     public boolean equals(final Object other) {
  70         if (other instanceof ScriptObjectMirror) {
  71             return sobj.equals(((ScriptObjectMirror)other).sobj);
  72         }
  73 
  74         return false;
  75     }
  76 
  77     @Override
  78     public int hashCode() {
  79         return sobj.hashCode();
  80     }
  81 
  82     @Override
  83     public String toString() {
  84         return inGlobal(new Callable<String>() {
  85             @Override
  86             public String call() {
  87                 return ScriptRuntime.safeToString(sobj);
  88             }
  89         });
  90     }
  91 
  92     // JSObject methods
  93     @Override
  94     public Object call(final String functionName, final Object... args) {
  95         final ScriptObject oldGlobal = Context.getGlobal();
  96         final boolean globalChanged = (oldGlobal != global);
  97 
  98         try {
  99             if (globalChanged) {
 100                 Context.setGlobal(global);
 101             }
 102 
 103             final Object val = functionName == null? sobj : sobj.get(functionName);
 104             if (val instanceof ScriptFunction) {
 105                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
 106                 return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
 107             } else if (val instanceof ScriptObjectMirror && ((ScriptObjectMirror)val).isFunction()) {
 108                 return ((ScriptObjectMirror)val).call(null, args);
 109             }
 110 
 111             throw new NoSuchMethodException("No such function " + ((functionName != null)? functionName : ""));
 112         } catch (final RuntimeException | Error e) {
 113             throw e;
 114         } catch (final Throwable t) {
 115             throw new RuntimeException(t);
 116         } finally {
 117             if (globalChanged) {
 118                 Context.setGlobal(oldGlobal);
 119             }
 120         }
 121     }
 122 
 123     @Override
 124     public Object newObject(final String functionName, final Object... args) {
 125         final ScriptObject oldGlobal = Context.getGlobal();
 126         final boolean globalChanged = (oldGlobal != global);
 127 
 128         try {
 129             if (globalChanged) {
 130                 Context.setGlobal(global);
 131             }
 132 
 133             final Object val = functionName == null? sobj : sobj.get(functionName);
 134             if (val instanceof ScriptFunction) {
 135                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
 136                 return wrap(ScriptRuntime.construct((ScriptFunction)val, unwrapArray(modArgs, global)), global);
 137             } else if (val instanceof ScriptObjectMirror && ((ScriptObjectMirror)val).isFunction()) {
 138                 return ((ScriptObjectMirror)val).newObject(null, args);
 139             }
 140 
 141             throw new RuntimeException("not a constructor " + ((functionName != null)? functionName : ""));
 142         } catch (final RuntimeException | Error e) {
 143             throw e;
 144         } catch (final Throwable t) {
 145             throw new RuntimeException(t);
 146         } finally {
 147             if (globalChanged) {
 148                 Context.setGlobal(oldGlobal);
 149             }
 150         }
 151     }
 152 
 153     @Override
 154     public Object eval(final String s) {
 155         return inGlobal(new Callable<Object>() {
 156             @Override
 157             public Object call() {
 158                 final Context context = AccessController.doPrivileged(
 159                         new PrivilegedAction<Context>() {
 160                             @Override
 161                             public Context run() {
 162                                 return Context.getContext();
 163                             }
 164                         }, GET_CONTEXT_ACC_CTXT);
 165                 return wrap(context.eval(global, s, null, null, false), global);
 166             }
 167         });
 168     }
 169 
 170     @Override
 171     public Object getMember(final String name) {
 172         return inGlobal(new Callable<Object>() {
 173             @Override public Object call() {
 174                 return wrap(sobj.get(name), global);
 175             }
 176         });
 177     }
 178 
 179     @Override
 180     public Object getSlot(final int index) {
 181         return inGlobal(new Callable<Object>() {
 182             @Override public Object call() {
 183                 return wrap(sobj.get(index), global);
 184             }
 185         });
 186     }
 187 
 188     @Override
 189     public void removeMember(final String name) {
 190         remove(name);
 191     }
 192 
 193     @Override
 194     public void setMember(final String name, final Object value) {
 195         put(name, value);
 196     }
 197 
 198     @Override
 199     public void setSlot(final int index, final Object value) {
 200         inGlobal(new Callable<Void>() {
 201             @Override public Void call() {
 202                 sobj.set(index, unwrap(value, global), strict);
 203                 return null;
 204             }
 205         });
 206     }
 207 
 208     // javax.script.Bindings methods
 209 
 210     @Override
 211     public void clear() {
 212         inGlobal(new Callable<Object>() {
 213             @Override public Object call() {
 214                 sobj.clear(strict);
 215                 return null;
 216             }
 217         });
 218     }
 219 
 220     @Override
 221     public boolean containsKey(final Object key) {
 222         return inGlobal(new Callable<Boolean>() {
 223             @Override public Boolean call() {
 224                 return sobj.containsKey(unwrap(key, global));
 225             }
 226         });
 227     }
 228 
 229     @Override
 230     public boolean containsValue(final Object value) {
 231         return inGlobal(new Callable<Boolean>() {
 232             @Override public Boolean call() {
 233                 return sobj.containsValue(unwrap(value, global));
 234             }
 235         });
 236     }
 237 
 238     @Override
 239     public Set<Map.Entry<String, Object>> entrySet() {
 240         return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() {
 241             @Override public Set<Map.Entry<String, Object>> call() {
 242                 final Iterator<String>               iter    = sobj.propertyIterator();
 243                 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>();
 244 
 245                 while (iter.hasNext()) {
 246                     final String key   = iter.next();
 247                     final Object value = translateUndefined(wrap(sobj.get(key), global));
 248                     entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
 249                 }
 250 
 251                 return Collections.unmodifiableSet(entries);
 252             }
 253         });
 254     }
 255 
 256     @Override
 257     public Object get(final Object key) {
 258         return inGlobal(new Callable<Object>() {
 259             @Override public Object call() {
 260                 return translateUndefined(wrap(sobj.get(key), global));
 261             }
 262         });
 263     }
 264 
 265     @Override
 266     public boolean isEmpty() {
 267         return inGlobal(new Callable<Boolean>() {
 268             @Override public Boolean call() {
 269                 return sobj.isEmpty();
 270             }
 271         });
 272     }
 273 
 274     @Override
 275     public Set<String> keySet() {
 276         return inGlobal(new Callable<Set<String>>() {
 277             @Override public Set<String> call() {
 278                 final Iterator<String> iter   = sobj.propertyIterator();
 279                 final Set<String>      keySet = new LinkedHashSet<>();
 280 
 281                 while (iter.hasNext()) {
 282                     keySet.add(iter.next());
 283                 }
 284 
 285                 return Collections.unmodifiableSet(keySet);
 286             }
 287         });
 288     }
 289 
 290     @Override
 291     public Object put(final String key, final Object value) {
 292         final ScriptObject oldGlobal = Context.getGlobal();
 293         final boolean globalChanged = (oldGlobal != global);
 294         return inGlobal(new Callable<Object>() {
 295             @Override public Object call() {
 296                 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
 297                 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global), strict), global));
 298             }
 299         });
 300     }
 301 
 302     @Override
 303     public void putAll(final Map<? extends String, ? extends Object> map) {
 304         final ScriptObject oldGlobal = Context.getGlobal();
 305         final boolean globalChanged = (oldGlobal != global);
 306         inGlobal(new Callable<Object>() {
 307             @Override public Object call() {
 308                 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) {
 309                     final Object value = entry.getValue();
 310                     final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
 311                     sobj.set(entry.getKey(), unwrap(modValue, global), strict);
 312                 }
 313                 return null;
 314             }
 315         });
 316     }
 317 
 318     @Override
 319     public Object remove(final Object key) {
 320         return inGlobal(new Callable<Object>() {
 321             @Override public Object call() {
 322                 return wrap(sobj.remove(unwrap(key, global), strict), global);
 323             }
 324         });
 325     }
 326 
 327     /**
 328      * Delete a property from this object.
 329      *
 330      * @param key the property to be deleted
 331      *
 332      * @return if the delete was successful or not
 333      */
 334     public boolean delete(final Object key) {
 335         return inGlobal(new Callable<Boolean>() {
 336             @Override public Boolean call() {
 337                 return sobj.delete(unwrap(key, global), strict);
 338             }
 339         });
 340     }
 341 
 342     @Override
 343     public int size() {
 344         return inGlobal(new Callable<Integer>() {
 345             @Override public Integer call() {
 346                 return sobj.size();
 347             }
 348         });
 349     }
 350 
 351     @Override
 352     public Collection<Object> values() {
 353         return inGlobal(new Callable<Collection<Object>>() {
 354             @Override public Collection<Object> call() {
 355                 final List<Object>     values = new ArrayList<>(size());
 356                 final Iterator<Object> iter   = sobj.valueIterator();
 357 
 358                 while (iter.hasNext()) {
 359                     values.add(translateUndefined(wrap(iter.next(), global)));
 360                 }
 361 
 362                 return Collections.unmodifiableList(values);
 363             }
 364         });
 365     }
 366 
 367     // Support for ECMAScript Object API on mirrors
 368 
 369     /**
 370      * Return the __proto__ of this object.
 371      * @return __proto__ object.
 372      */
 373     public Object getProto() {
 374         return inGlobal(new Callable<Object>() {
 375             @Override public Object call() {
 376                 return wrap(sobj.getProto(), global);
 377             }
 378         });
 379     }
 380 
 381     /**
 382      * Set the __proto__ of this object.
 383      * @param proto new proto for this object
 384      */
 385     public void setProto(final Object proto) {
 386         inGlobal(new Callable<Void>() {
 387             @Override public Void call() {
 388                 sobj.setProtoCheck(unwrap(proto, global));
 389                 return null;
 390             }
 391         });
 392     }
 393 
 394     /**
 395      * ECMA [[Class]] property
 396      *
 397      * @return ECMA [[Class]] property value of this object
 398      */
 399     public String getClassName() {
 400         return sobj.getClassName();
 401     }
 402 
 403     /**
 404      * ECMA 8.12.1 [[GetOwnProperty]] (P)
 405      *
 406      * @param key property key
 407      *
 408      * @return Returns the Property Descriptor of the named own property of this
 409      * object, or undefined if absent.
 410      */
 411     public Object getOwnPropertyDescriptor(final String key) {
 412         return inGlobal(new Callable<Object>() {
 413             @Override public Object call() {
 414                 return wrap(sobj.getOwnPropertyDescriptor(key), global);
 415             }
 416         });
 417     }
 418 
 419     /**
 420      * return an array of own property keys associated with the object.
 421      *
 422      * @param all True if to include non-enumerable keys.
 423      * @return Array of keys.
 424      */
 425     public String[] getOwnKeys(final boolean all) {
 426         return inGlobal(new Callable<String[]>() {
 427             @Override public String[] call() {
 428                 return sobj.getOwnKeys(all);
 429             }
 430         });
 431     }
 432 
 433     /**
 434      * Flag this script object as non extensible
 435      *
 436      * @return the object after being made non extensible
 437      */
 438     public ScriptObjectMirror preventExtensions() {
 439         return inGlobal(new Callable<ScriptObjectMirror>() {
 440             @Override public ScriptObjectMirror call() {
 441                 sobj.preventExtensions();
 442                 return ScriptObjectMirror.this;
 443             }
 444         });
 445     }
 446 
 447     /**
 448      * Check if this script object is extensible
 449      * @return true if extensible
 450      */
 451     public boolean isExtensible() {
 452         return inGlobal(new Callable<Boolean>() {
 453             @Override public Boolean call() {
 454                 return sobj.isExtensible();
 455             }
 456         });
 457     }
 458 
 459     /**
 460      * ECMAScript 15.2.3.8 - seal implementation
 461      * @return the sealed script object
 462      */
 463     public ScriptObjectMirror seal() {
 464         return inGlobal(new Callable<ScriptObjectMirror>() {
 465             @Override public ScriptObjectMirror call() {
 466                 sobj.seal();
 467                 return ScriptObjectMirror.this;
 468             }
 469         });
 470     }
 471 
 472     /**
 473      * Check whether this script object is sealed
 474      * @return true if sealed
 475      */
 476     public boolean isSealed() {
 477         return inGlobal(new Callable<Boolean>() {
 478             @Override public Boolean call() {
 479                 return sobj.isSealed();
 480             }
 481         });
 482     }
 483 
 484     /**
 485      * ECMA 15.2.39 - freeze implementation. Freeze this script object
 486      * @return the frozen script object
 487      */
 488     public ScriptObjectMirror freeze() {
 489         return inGlobal(new Callable<ScriptObjectMirror>() {
 490             @Override public ScriptObjectMirror call() {
 491                 sobj.freeze();
 492                 return ScriptObjectMirror.this;
 493             }
 494         });
 495     }
 496 
 497     /**
 498      * Check whether this script object is frozen
 499      * @return true if frozen
 500      */
 501     public boolean isFrozen() {
 502         return inGlobal(new Callable<Boolean>() {
 503             @Override public Boolean call() {
 504                 return sobj.isFrozen();
 505             }
 506         });
 507     }
 508 
 509     // ECMAScript instanceof check
 510 
 511     /**
 512      * Checking whether a script object is an instance of another by
 513      * walking the proto chain
 514      *
 515      * @param instance instace to check
 516      * @return true if 'instance' is an instance of this object
 517      */
 518     public boolean isInstance(final ScriptObjectMirror instance) {
 519         // if not belongs to my global scope, return false
 520         if (instance == null || global != instance.global) {
 521             return false;
 522         }
 523 
 524         return inGlobal(new Callable<Boolean>() {
 525             @Override public Boolean call() {
 526                 return sobj.isInstance(instance.sobj);
 527             }
 528         });
 529     }
 530 
 531     /**
 532      * is this a function object?
 533      *
 534      * @return if this mirror wraps a ECMAScript function instance
 535      */
 536     public boolean isFunction() {
 537         return sobj instanceof ScriptFunction;
 538     }
 539 
 540     /**
 541      * is this a 'use strict' function object?
 542      *
 543      * @return true if this mirror represents a ECMAScript 'use strict' function
 544      */
 545     public boolean isStrictFunction() {
 546         return isFunction() && ((ScriptFunction)sobj).isStrict();
 547     }
 548 
 549     /**
 550      * is this an array object?
 551      *
 552      * @return if this mirror wraps a ECMAScript array object
 553      */
 554     public boolean isArray() {
 555         return sobj.isArray();
 556     }
 557 
 558     /**
 559      * Utility to check if given object is ECMAScript undefined value
 560      *
 561      * @param obj object to check
 562      * @return true if 'obj' is ECMAScript undefined value
 563      */
 564     public static boolean isUndefined(final Object obj) {
 565         return obj == ScriptRuntime.UNDEFINED;
 566     }
 567 
 568     /**
 569      * Make a script object mirror on given object if needed.
 570      *
 571      * @param obj object to be wrapped
 572      * @param homeGlobal global to which this object belongs
 573      * @return wrapped object
 574      */
 575     public static Object wrap(final Object obj, final ScriptObject homeGlobal) {
 576         return (obj instanceof ScriptObject && homeGlobal != null) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj;
 577     }
 578 
 579     /**
 580      * Unwrap a script object mirror if needed.
 581      *
 582      * @param obj object to be unwrapped
 583      * @param homeGlobal global to which this object belongs
 584      * @return unwrapped object
 585      */
 586     public static Object unwrap(final Object obj, final ScriptObject homeGlobal) {
 587         if (obj instanceof ScriptObjectMirror) {
 588             final ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
 589             return (mirror.global == homeGlobal)? mirror.sobj : obj;
 590         }
 591 
 592         return obj;
 593     }
 594 
 595     /**
 596      * Wrap an array of object to script object mirrors if needed.
 597      *
 598      * @param args array to be unwrapped
 599      * @param homeGlobal global to which this object belongs
 600      * @return wrapped array
 601      */
 602     public static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) {
 603         if (args == null || args.length == 0) {
 604             return args;
 605         }
 606 
 607         final Object[] newArgs = new Object[args.length];
 608         int index = 0;
 609         for (final Object obj : args) {
 610             newArgs[index] = wrap(obj, homeGlobal);
 611             index++;
 612         }
 613         return newArgs;
 614     }
 615 
 616     /**
 617      * Unwrap an array of script object mirrors if needed.
 618      *
 619      * @param args array to be unwrapped
 620      * @param homeGlobal global to which this object belongs
 621      * @return unwrapped array
 622      */
 623     public static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) {
 624         if (args == null || args.length == 0) {
 625             return args;
 626         }
 627 
 628         final Object[] newArgs = new Object[args.length];
 629         int index = 0;
 630         for (final Object obj : args) {
 631             newArgs[index] = unwrap(obj, homeGlobal);
 632             index++;
 633         }
 634         return newArgs;
 635     }
 636 
 637     // package-privates below this.
 638 
 639     ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) {
 640         assert sobj != null : "ScriptObjectMirror on null!";
 641         assert global instanceof GlobalObject : "global is not a GlobalObject";
 642 
 643         this.sobj = sobj;
 644         this.global = global;
 645         this.strict = ((GlobalObject)global).isStrictContext();
 646     }
 647 
 648     // accessors for script engine
 649     ScriptObject getScriptObject() {
 650         return sobj;
 651     }
 652 
 653     ScriptObject getHomeGlobal() {
 654         return global;
 655     }
 656 
 657     static Object translateUndefined(Object obj) {
 658         return (obj == ScriptRuntime.UNDEFINED)? null : obj;
 659     }
 660 
 661     // internals only below this.
 662     private <V> V inGlobal(final Callable<V> callable) {
 663         final ScriptObject oldGlobal = Context.getGlobal();
 664         final boolean globalChanged = (oldGlobal != global);
 665         if (globalChanged) {
 666             Context.setGlobal(global);
 667         }
 668         try {
 669             return callable.call();
 670         } catch (final RuntimeException e) {
 671             throw e;
 672         } catch (final Exception e) {
 673             throw new AssertionError("Cannot happen", e);
 674         } finally {
 675             if (globalChanged) {
 676                 Context.setGlobal(oldGlobal);
 677             }
 678         }
 679     }
 680 }