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.ScriptObject 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 }