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 8.12.1 [[GetOwnProperty]] (P) 395 * 396 * @param key property key 397 * 398 * @return Returns the Property Descriptor of the named own property of this 399 * object, or undefined if absent. 400 */ 401 public Object getOwnPropertyDescriptor(final String key) { 402 return inGlobal(new Callable<Object>() { 403 @Override public Object call() { 404 return wrap(sobj.getOwnPropertyDescriptor(key), global); 405 } 406 }); 407 } 408 409 /** 410 * return an array of own property keys associated with the object. 411 * 412 * @param all True if to include non-enumerable keys. 413 * @return Array of keys. 414 */ 415 public String[] getOwnKeys(final boolean all) { 416 return inGlobal(new Callable<String[]>() { 417 @Override public String[] call() { 418 return sobj.getOwnKeys(all); 419 } 420 }); 421 } 422 423 /** 424 * Flag this script object as non extensible 425 * 426 * @return the object after being made non extensible 427 */ 428 public ScriptObjectMirror preventExtensions() { 429 return inGlobal(new Callable<ScriptObjectMirror>() { 430 @Override public ScriptObjectMirror call() { 431 sobj.preventExtensions(); 432 return ScriptObjectMirror.this; 433 } 434 }); 435 } 436 437 /** 438 * Check if this script object is extensible 439 * @return true if extensible 440 */ 441 public boolean isExtensible() { 442 return inGlobal(new Callable<Boolean>() { 443 @Override public Boolean call() { 444 return sobj.isExtensible(); 445 } 446 }); 447 } 448 449 /** 450 * ECMAScript 15.2.3.8 - seal implementation 451 * @return the sealed script object 452 */ 453 public ScriptObjectMirror seal() { 454 return inGlobal(new Callable<ScriptObjectMirror>() { 455 @Override public ScriptObjectMirror call() { 456 sobj.seal(); 457 return ScriptObjectMirror.this; 458 } 459 }); 460 } 461 462 /** 463 * Check whether this script object is sealed 464 * @return true if sealed 465 */ 466 public boolean isSealed() { 467 return inGlobal(new Callable<Boolean>() { 468 @Override public Boolean call() { 469 return sobj.isSealed(); 470 } 471 }); 472 } 473 474 /** 475 * ECMA 15.2.39 - freeze implementation. Freeze this script object 476 * @return the frozen script object 477 */ 478 public ScriptObjectMirror freeze() { 479 return inGlobal(new Callable<ScriptObjectMirror>() { 480 @Override public ScriptObjectMirror call() { 481 sobj.freeze(); 482 return ScriptObjectMirror.this; 483 } 484 }); 485 } 486 487 /** 488 * Check whether this script object is frozen 489 * @return true if frozen 490 */ 491 public boolean isFrozen() { 492 return inGlobal(new Callable<Boolean>() { 493 @Override public Boolean call() { 494 return sobj.isFrozen(); 495 } 496 }); 497 } 498 499 // ECMAScript instanceof check 500 501 /** 502 * Checking whether a script object is an instance of another by 503 * walking the proto chain 504 * 505 * @param instance instace to check 506 * @return true if 'instance' is an instance of this object 507 */ 508 public boolean isInstance(final ScriptObjectMirror instance) { 509 // if not belongs to my global scope, return false 510 if (instance == null || global != instance.global) { 511 return false; 512 } 513 514 return inGlobal(new Callable<Boolean>() { 515 @Override public Boolean call() { 516 return sobj.isInstance(instance.sobj); 517 } 518 }); 519 } 520 521 /** 522 * is this a function object? 523 * 524 * @return if this mirror wraps a ECMAScript function instance 525 */ 526 public boolean isFunction() { 527 return sobj instanceof ScriptFunction; 528 } 529 530 /** 531 * is this a 'use strict' function object? 532 * 533 * @return true if this mirror represents a ECMAScript 'use strict' function 534 */ 535 public boolean isStrictFunction() { 536 return isFunction() && ((ScriptFunction)sobj).isStrict(); 537 } 538 539 /** 540 * is this an array object? 541 * 542 * @return if this mirror wraps a ECMAScript array object 543 */ 544 public boolean isArray() { 545 return sobj.isArray(); 546 } 547 548 /** 549 * Utility to check if given object is ECMAScript undefined value 550 * 551 * @param obj object to check 552 * @return true if 'obj' is ECMAScript undefined value 553 */ 554 public static boolean isUndefined(final Object obj) { 555 return obj == ScriptRuntime.UNDEFINED; 556 } 557 558 /** 559 * Make a script object mirror on given object if needed. 560 * 561 * @param obj object to be wrapped 562 * @param homeGlobal global to which this object belongs 563 * @return wrapped object 564 */ 565 public static Object wrap(final Object obj, final ScriptObject homeGlobal) { 566 return (obj instanceof ScriptObject && homeGlobal != null) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj; 567 } 568 569 /** 570 * Unwrap a script object mirror if needed. 571 * 572 * @param obj object to be unwrapped 573 * @param homeGlobal global to which this object belongs 574 * @return unwrapped object 575 */ 576 public static Object unwrap(final Object obj, final ScriptObject homeGlobal) { 577 if (obj instanceof ScriptObjectMirror) { 578 final ScriptObjectMirror mirror = (ScriptObjectMirror)obj; 579 return (mirror.global == homeGlobal)? mirror.sobj : obj; 580 } 581 582 return obj; 583 } 584 585 /** 586 * Wrap an array of object to script object mirrors if needed. 587 * 588 * @param args array to be unwrapped 589 * @param homeGlobal global to which this object belongs 590 * @return wrapped array 591 */ 592 public static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) { 593 if (args == null || args.length == 0) { 594 return args; 595 } 596 597 final Object[] newArgs = new Object[args.length]; 598 int index = 0; 599 for (final Object obj : args) { 600 newArgs[index] = wrap(obj, homeGlobal); 601 index++; 602 } 603 return newArgs; 604 } 605 606 /** 607 * Unwrap an array of script object mirrors if needed. 608 * 609 * @param args array to be unwrapped 610 * @param homeGlobal global to which this object belongs 611 * @return unwrapped array 612 */ 613 public static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) { 614 if (args == null || args.length == 0) { 615 return args; 616 } 617 618 final Object[] newArgs = new Object[args.length]; 619 int index = 0; 620 for (final Object obj : args) { 621 newArgs[index] = unwrap(obj, homeGlobal); 622 index++; 623 } 624 return newArgs; 625 } 626 627 // package-privates below this. 628 629 ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) { 630 assert sobj != null : "ScriptObjectMirror on null!"; 631 assert global != null : "null global for ScriptObjectMirror!"; 632 633 this.sobj = sobj; 634 this.global = global; 635 } 636 637 // accessors for script engine 638 ScriptObject getScriptObject() { 639 return sobj; 640 } 641 642 ScriptObject getHomeGlobal() { 643 return global; 644 } 645 646 static Object translateUndefined(Object obj) { 647 return (obj == ScriptRuntime.UNDEFINED)? null : obj; 648 } 649 650 // internals only below this. 651 private <V> V inGlobal(final Callable<V> callable) { 652 final ScriptObject oldGlobal = Context.getGlobal(); 653 final boolean globalChanged = (oldGlobal != global); 654 if (globalChanged) { 655 Context.setGlobal(global); 656 } 657 try { 658 return callable.call(); 659 } catch (final RuntimeException e) { 660 throw e; 661 } catch (final Exception e) { 662 throw new AssertionError("Cannot happen", e); 663 } finally { 664 if (globalChanged) { 665 Context.setGlobal(oldGlobal); 666 } 667 } 668 } 669 }