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