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