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 }