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