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