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