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