1 /* 2 * Copyright (c) 2010, 2016, 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.internal.objects; 27 28 import static jdk.dynalink.StandardNamespace.METHOD; 29 import static jdk.dynalink.StandardNamespace.PROPERTY; 30 import static jdk.dynalink.StandardOperation.GET; 31 import static jdk.dynalink.StandardOperation.SET; 32 import static jdk.nashorn.internal.lookup.Lookup.MH; 33 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 34 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 35 36 import java.lang.invoke.MethodHandle; 37 import java.lang.invoke.MethodHandles; 38 import java.lang.invoke.MethodType; 39 import java.nio.ByteBuffer; 40 import java.util.ArrayList; 41 import java.util.Collection; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Set; 45 import java.util.concurrent.Callable; 46 import jdk.dynalink.CallSiteDescriptor; 47 import jdk.dynalink.Operation; 48 import jdk.dynalink.beans.BeansLinker; 49 import jdk.dynalink.beans.StaticClass; 50 import jdk.dynalink.linker.GuardedInvocation; 51 import jdk.dynalink.linker.GuardingDynamicLinker; 52 import jdk.dynalink.linker.LinkRequest; 53 import jdk.dynalink.linker.support.SimpleLinkRequest; 54 import jdk.nashorn.api.scripting.ScriptObjectMirror; 55 import jdk.nashorn.internal.lookup.Lookup; 56 import jdk.nashorn.internal.objects.annotations.Attribute; 57 import jdk.nashorn.internal.objects.annotations.Constructor; 58 import jdk.nashorn.internal.objects.annotations.Function; 59 import jdk.nashorn.internal.objects.annotations.ScriptClass; 60 import jdk.nashorn.internal.objects.annotations.Where; 61 import jdk.nashorn.internal.runtime.AccessorProperty; 62 import jdk.nashorn.internal.runtime.ECMAException; 63 import jdk.nashorn.internal.runtime.JSType; 64 import jdk.nashorn.internal.runtime.Property; 65 import jdk.nashorn.internal.runtime.PropertyMap; 66 import jdk.nashorn.internal.runtime.ScriptObject; 67 import jdk.nashorn.internal.runtime.ScriptRuntime; 68 import jdk.nashorn.internal.runtime.arrays.ArrayData; 69 import jdk.nashorn.internal.runtime.arrays.ArrayIndex; 70 import jdk.nashorn.internal.runtime.linker.Bootstrap; 71 import jdk.nashorn.internal.runtime.linker.InvokeByName; 72 import jdk.nashorn.internal.runtime.linker.NashornBeansLinker; 73 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 74 75 /** 76 * ECMA 15.2 Object objects 77 * 78 * JavaScript Object constructor/prototype. Note: instances of this class are 79 * never created. This class is not even a subclass of ScriptObject. But, we use 80 * this class to generate prototype and constructor for "Object". 81 * 82 */ 83 @ScriptClass("Object") 84 public final class NativeObject { 85 /** Methodhandle to proto getter */ 86 public static final MethodHandle GET__PROTO__ = findOwnMH("get__proto__", ScriptObject.class, Object.class); 87 88 /** Methodhandle to proto setter */ 89 public static final MethodHandle SET__PROTO__ = findOwnMH("set__proto__", Object.class, Object.class, Object.class); 90 91 private static final Object TO_STRING = new Object(); 92 93 private static InvokeByName getTO_STRING() { 94 return Global.instance().getInvokeByName(TO_STRING, 95 new Callable<InvokeByName>() { 96 @Override 97 public InvokeByName call() { 98 return new InvokeByName("toString", ScriptObject.class); 99 } 100 }); 101 } 102 103 private static final Operation GET_METHOD = GET.withNamespace(METHOD); 104 private static final Operation GET_PROPERTY = GET.withNamespace(PROPERTY); 105 private static final Operation SET_PROPERTY = SET.withNamespace(PROPERTY); 106 107 @SuppressWarnings("unused") 108 private static ScriptObject get__proto__(final Object self) { 109 // See ES6 draft spec: B.2.2.1.1 get Object.prototype.__proto__ 110 // Step 1 Let O be the result of calling ToObject passing the this. 111 final ScriptObject sobj = Global.checkObject(Global.toObject(self)); 112 return sobj.getProto(); 113 } 114 115 @SuppressWarnings("unused") 116 private static Object set__proto__(final Object self, final Object proto) { 117 // See ES6 draft spec: B.2.2.1.2 set Object.prototype.__proto__ 118 // Step 1 119 Global.checkObjectCoercible(self); 120 // Step 4 121 if (! (self instanceof ScriptObject)) { 122 return UNDEFINED; 123 } 124 125 final ScriptObject sobj = (ScriptObject)self; 126 // __proto__ assignment ignores non-nulls and non-objects 127 // step 3: If Type(proto) is neither Object nor Null, then return undefined. 128 if (proto == null || proto instanceof ScriptObject) { 129 sobj.setPrototypeOf(proto); 130 } 131 return UNDEFINED; 132 } 133 134 private static final MethodType MIRROR_GETTER_TYPE = MethodType.methodType(Object.class, ScriptObjectMirror.class); 135 private static final MethodType MIRROR_SETTER_TYPE = MethodType.methodType(Object.class, ScriptObjectMirror.class, Object.class); 136 137 // initialized by nasgen 138 @SuppressWarnings("unused") 139 private static PropertyMap $nasgenmap$; 140 141 private NativeObject() { 142 // don't create me! 143 throw new UnsupportedOperationException(); 144 } 145 146 private static ECMAException notAnObject(final Object obj) { 147 return typeError("not.an.object", ScriptRuntime.safeToString(obj)); 148 } 149 150 /** 151 * Nashorn extension: setIndexedPropertiesToExternalArrayData 152 * 153 * @param self self reference 154 * @param obj object whose index properties are backed by buffer 155 * @param buf external buffer - should be a nio ByteBuffer 156 * @return the 'obj' object 157 */ 158 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 159 public static ScriptObject setIndexedPropertiesToExternalArrayData(final Object self, final Object obj, final Object buf) { 160 Global.checkObject(obj); 161 final ScriptObject sobj = (ScriptObject)obj; 162 if (buf instanceof ByteBuffer) { 163 sobj.setArray(ArrayData.allocate((ByteBuffer)buf)); 164 } else { 165 throw typeError("not.a.bytebuffer", "setIndexedPropertiesToExternalArrayData's buf argument"); 166 } 167 return sobj; 168 } 169 170 171 /** 172 * ECMA 15.2.3.2 Object.getPrototypeOf ( O ) 173 * 174 * @param self self reference 175 * @param obj object to get prototype from 176 * @return the prototype of an object 177 */ 178 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 179 public static Object getPrototypeOf(final Object self, final Object obj) { 180 if (obj instanceof ScriptObject) { 181 return ((ScriptObject)obj).getProto(); 182 } else if (obj instanceof ScriptObjectMirror) { 183 return ((ScriptObjectMirror)obj).getProto(); 184 } else { 185 final JSType type = JSType.of(obj); 186 if (type == JSType.OBJECT) { 187 // host (Java) objects have null __proto__ 188 return null; 189 } 190 191 // must be some JS primitive 192 throw notAnObject(obj); 193 } 194 } 195 196 /** 197 * Nashorn extension: Object.setPrototypeOf ( O, proto ) 198 * Also found in ES6 draft specification. 199 * 200 * @param self self reference 201 * @param obj object to set prototype for 202 * @param proto prototype object to be used 203 * @return object whose prototype is set 204 */ 205 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 206 public static Object setPrototypeOf(final Object self, final Object obj, final Object proto) { 207 if (obj instanceof ScriptObject) { 208 ((ScriptObject)obj).setPrototypeOf(proto); 209 return obj; 210 } else if (obj instanceof ScriptObjectMirror) { 211 ((ScriptObjectMirror)obj).setProto(proto); 212 return obj; 213 } 214 215 throw notAnObject(obj); 216 } 217 218 /** 219 * ECMA 15.2.3.3 Object.getOwnPropertyDescriptor ( O, P ) 220 * 221 * @param self self reference 222 * @param obj object from which to get property descriptor for {@code ToString(prop)} 223 * @param prop property descriptor 224 * @return property descriptor 225 */ 226 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 227 public static Object getOwnPropertyDescriptor(final Object self, final Object obj, final Object prop) { 228 if (obj instanceof ScriptObject) { 229 final String key = JSType.toString(prop); 230 final ScriptObject sobj = (ScriptObject)obj; 231 232 return sobj.getOwnPropertyDescriptor(key); 233 } else if (obj instanceof ScriptObjectMirror) { 234 final String key = JSType.toString(prop); 235 final ScriptObjectMirror sobjMirror = (ScriptObjectMirror)obj; 236 237 return sobjMirror.getOwnPropertyDescriptor(key); 238 } else { 239 throw notAnObject(obj); 240 } 241 } 242 243 /** 244 * ECMA 15.2.3.4 Object.getOwnPropertyNames ( O ) 245 * 246 * @param self self reference 247 * @param obj object to query for property names 248 * @return array of property names 249 */ 250 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 251 public static ScriptObject getOwnPropertyNames(final Object self, final Object obj) { 252 if (obj instanceof ScriptObject) { 253 return new NativeArray(((ScriptObject)obj).getOwnKeys(true)); 254 } else if (obj instanceof ScriptObjectMirror) { 255 return new NativeArray(((ScriptObjectMirror)obj).getOwnKeys(true)); 256 } else { 257 throw notAnObject(obj); 258 } 259 } 260 261 /** 262 * ECMA 2 19.1.2.8 Object.getOwnPropertySymbols ( O ) 263 * 264 * @param self self reference 265 * @param obj object to query for property names 266 * @return array of property names 267 */ 268 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 269 public static ScriptObject getOwnPropertySymbols(final Object self, final Object obj) { 270 if (obj instanceof ScriptObject) { 271 return new NativeArray(((ScriptObject)obj).getOwnSymbols(true)); 272 } else { 273 // TODO: we don't support this on ScriptObjectMirror objects yet 274 throw notAnObject(obj); 275 } 276 } 277 278 /** 279 * ECMA 15.2.3.5 Object.create ( O [, Properties] ) 280 * 281 * @param self self reference 282 * @param proto prototype object 283 * @param props properties to define 284 * @return object created 285 */ 286 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 287 public static ScriptObject create(final Object self, final Object proto, final Object props) { 288 if (proto != null) { 289 Global.checkObject(proto); 290 } 291 292 // FIXME: should we create a proper object with correct number of 293 // properties? 294 final ScriptObject newObj = Global.newEmptyInstance(); 295 newObj.setProto((ScriptObject)proto); 296 if (props != UNDEFINED) { 297 NativeObject.defineProperties(self, newObj, props); 298 } 299 300 return newObj; 301 } 302 303 /** 304 * ECMA 15.2.3.6 Object.defineProperty ( O, P, Attributes ) 305 * 306 * @param self self reference 307 * @param obj object in which to define a property 308 * @param prop property to define 309 * @param attr attributes for property descriptor 310 * @return object 311 */ 312 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 313 public static ScriptObject defineProperty(final Object self, final Object obj, final Object prop, final Object attr) { 314 final ScriptObject sobj = Global.checkObject(obj); 315 sobj.defineOwnProperty(JSType.toPropertyKey(prop), attr, true); 316 return sobj; 317 } 318 319 /** 320 * ECMA 5.2.3.7 Object.defineProperties ( O, Properties ) 321 * 322 * @param self self reference 323 * @param obj object in which to define properties 324 * @param props properties 325 * @return object 326 */ 327 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 328 public static ScriptObject defineProperties(final Object self, final Object obj, final Object props) { 329 final ScriptObject sobj = Global.checkObject(obj); 330 final Object propsObj = Global.toObject(props); 331 332 if (propsObj instanceof ScriptObject) { 333 final Object[] keys = ((ScriptObject)propsObj).getOwnKeys(false); 334 for (final Object key : keys) { 335 final String prop = JSType.toString(key); 336 sobj.defineOwnProperty(prop, ((ScriptObject)propsObj).get(prop), true); 337 } 338 } 339 return sobj; 340 } 341 342 /** 343 * ECMA 15.2.3.8 Object.seal ( O ) 344 * 345 * @param self self reference 346 * @param obj object to seal 347 * @return sealed object 348 */ 349 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 350 public static Object seal(final Object self, final Object obj) { 351 if (obj instanceof ScriptObject) { 352 return ((ScriptObject)obj).seal(); 353 } else if (obj instanceof ScriptObjectMirror) { 354 return ((ScriptObjectMirror)obj).seal(); 355 } else { 356 throw notAnObject(obj); 357 } 358 } 359 360 361 /** 362 * ECMA 15.2.3.9 Object.freeze ( O ) 363 * 364 * @param self self reference 365 * @param obj object to freeze 366 * @return frozen object 367 */ 368 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 369 public static Object freeze(final Object self, final Object obj) { 370 if (obj instanceof ScriptObject) { 371 return ((ScriptObject)obj).freeze(); 372 } else if (obj instanceof ScriptObjectMirror) { 373 return ((ScriptObjectMirror)obj).freeze(); 374 } else { 375 throw notAnObject(obj); 376 } 377 } 378 379 /** 380 * ECMA 15.2.3.10 Object.preventExtensions ( O ) 381 * 382 * @param self self reference 383 * @param obj object, for which to set the internal extensible property to false 384 * @return object 385 */ 386 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 387 public static Object preventExtensions(final Object self, final Object obj) { 388 if (obj instanceof ScriptObject) { 389 return ((ScriptObject)obj).preventExtensions(); 390 } else if (obj instanceof ScriptObjectMirror) { 391 return ((ScriptObjectMirror)obj).preventExtensions(); 392 } else { 393 throw notAnObject(obj); 394 } 395 } 396 397 /** 398 * ECMA 15.2.3.11 Object.isSealed ( O ) 399 * 400 * @param self self reference 401 * @param obj check whether an object is sealed 402 * @return true if sealed, false otherwise 403 */ 404 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 405 public static boolean isSealed(final Object self, final Object obj) { 406 if (obj instanceof ScriptObject) { 407 return ((ScriptObject)obj).isSealed(); 408 } else if (obj instanceof ScriptObjectMirror) { 409 return ((ScriptObjectMirror)obj).isSealed(); 410 } else { 411 throw notAnObject(obj); 412 } 413 } 414 415 /** 416 * ECMA 15.2.3.12 Object.isFrozen ( O ) 417 * 418 * @param self self reference 419 * @param obj check whether an object 420 * @return true if object is frozen, false otherwise 421 */ 422 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 423 public static boolean isFrozen(final Object self, final Object obj) { 424 if (obj instanceof ScriptObject) { 425 return ((ScriptObject)obj).isFrozen(); 426 } else if (obj instanceof ScriptObjectMirror) { 427 return ((ScriptObjectMirror)obj).isFrozen(); 428 } else { 429 throw notAnObject(obj); 430 } 431 } 432 433 /** 434 * ECMA 15.2.3.13 Object.isExtensible ( O ) 435 * 436 * @param self self reference 437 * @param obj check whether an object is extensible 438 * @return true if object is extensible, false otherwise 439 */ 440 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 441 public static boolean isExtensible(final Object self, final Object obj) { 442 if (obj instanceof ScriptObject) { 443 return ((ScriptObject)obj).isExtensible(); 444 } else if (obj instanceof ScriptObjectMirror) { 445 return ((ScriptObjectMirror)obj).isExtensible(); 446 } else { 447 throw notAnObject(obj); 448 } 449 } 450 451 /** 452 * ECMA 15.2.3.14 Object.keys ( O ) 453 * 454 * @param self self reference 455 * @param obj object from which to extract keys 456 * @return array of keys in object 457 */ 458 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 459 public static ScriptObject keys(final Object self, final Object obj) { 460 if (obj instanceof ScriptObject) { 461 final ScriptObject sobj = (ScriptObject)obj; 462 return new NativeArray(sobj.getOwnKeys(false)); 463 } else if (obj instanceof ScriptObjectMirror) { 464 final ScriptObjectMirror sobjMirror = (ScriptObjectMirror)obj; 465 return new NativeArray(sobjMirror.getOwnKeys(false)); 466 } else { 467 throw notAnObject(obj); 468 } 469 } 470 471 /** 472 * ECMA 15.2.2.1 , 15.2.1.1 new Object([value]) and Object([value]) 473 * 474 * Constructor 475 * 476 * @param newObj is the new object instantiated with the new operator 477 * @param self self reference 478 * @param value value of object to be instantiated 479 * @return the new NativeObject 480 */ 481 @Constructor 482 public static Object construct(final boolean newObj, final Object self, final Object value) { 483 final JSType type = JSType.ofNoFunction(value); 484 485 // Object(null), Object(undefined), Object() are same as "new Object()" 486 487 if (newObj || type == JSType.NULL || type == JSType.UNDEFINED) { 488 switch (type) { 489 case BOOLEAN: 490 case NUMBER: 491 case STRING: 492 case SYMBOL: 493 return Global.toObject(value); 494 case OBJECT: 495 return value; 496 case NULL: 497 case UNDEFINED: 498 // fall through.. 499 default: 500 break; 501 } 502 503 return Global.newEmptyInstance(); 504 } 505 506 return Global.toObject(value); 507 } 508 509 /** 510 * ECMA 15.2.4.2 Object.prototype.toString ( ) 511 * 512 * @param self self reference 513 * @return ToString of object 514 */ 515 @Function(attributes = Attribute.NOT_ENUMERABLE) 516 public static String toString(final Object self) { 517 return ScriptRuntime.builtinObjectToString(self); 518 } 519 520 /** 521 * ECMA 15.2.4.3 Object.prototype.toLocaleString ( ) 522 * 523 * @param self self reference 524 * @return localized ToString 525 */ 526 @Function(attributes = Attribute.NOT_ENUMERABLE) 527 public static Object toLocaleString(final Object self) { 528 final Object obj = JSType.toScriptObject(self); 529 if (obj instanceof ScriptObject) { 530 final InvokeByName toStringInvoker = getTO_STRING(); 531 final ScriptObject sobj = (ScriptObject)obj; 532 try { 533 final Object toString = toStringInvoker.getGetter().invokeExact(sobj); 534 535 if (Bootstrap.isCallable(toString)) { 536 return toStringInvoker.getInvoker().invokeExact(toString, sobj); 537 } 538 } catch (final RuntimeException | Error e) { 539 throw e; 540 } catch (final Throwable t) { 541 throw new RuntimeException(t); 542 } 543 544 throw typeError("not.a.function", "toString"); 545 } 546 547 return ScriptRuntime.builtinObjectToString(self); 548 } 549 550 /** 551 * ECMA 15.2.4.4 Object.prototype.valueOf ( ) 552 * 553 * @param self self reference 554 * @return value of object 555 */ 556 @Function(attributes = Attribute.NOT_ENUMERABLE) 557 public static Object valueOf(final Object self) { 558 return Global.toObject(self); 559 } 560 561 /** 562 * ECMA 15.2.4.5 Object.prototype.hasOwnProperty (V) 563 * 564 * @param self self reference 565 * @param v property to check for 566 * @return true if property exists in object 567 */ 568 @Function(attributes = Attribute.NOT_ENUMERABLE) 569 public static boolean hasOwnProperty(final Object self, final Object v) { 570 // Convert ScriptObjects to primitive with String.class hint 571 // but no need to convert other primitives to string. 572 final Object key = JSType.toPrimitive(v, String.class); 573 final Object obj = Global.toObject(self); 574 575 return obj instanceof ScriptObject && ((ScriptObject)obj).hasOwnProperty(key); 576 } 577 578 /** 579 * ECMA 15.2.4.6 Object.prototype.isPrototypeOf (V) 580 * 581 * @param self self reference 582 * @param v v prototype object to check against 583 * @return true if object is prototype of v 584 */ 585 @Function(attributes = Attribute.NOT_ENUMERABLE) 586 public static boolean isPrototypeOf(final Object self, final Object v) { 587 if (!(v instanceof ScriptObject)) { 588 return false; 589 } 590 591 final Object obj = Global.toObject(self); 592 ScriptObject proto = (ScriptObject)v; 593 594 do { 595 proto = proto.getProto(); 596 if (proto == obj) { 597 return true; 598 } 599 } while (proto != null); 600 601 return false; 602 } 603 604 /** 605 * ECMA 15.2.4.7 Object.prototype.propertyIsEnumerable (V) 606 * 607 * @param self self reference 608 * @param v property to check if enumerable 609 * @return true if property is enumerable 610 */ 611 @Function(attributes = Attribute.NOT_ENUMERABLE) 612 public static boolean propertyIsEnumerable(final Object self, final Object v) { 613 final String str = JSType.toString(v); 614 final Object obj = Global.toObject(self); 615 616 if (obj instanceof ScriptObject) { 617 final ScriptObject sobj = (ScriptObject) obj; 618 final Property property = sobj.getProperty(str); 619 if (property != null) { 620 return property.isEnumerable(); 621 } else { 622 return (sobj.getArray().has(ArrayIndex.getArrayIndex(v))); 623 } 624 } 625 626 return false; 627 } 628 629 /** 630 * Nashorn extension: Object.bindProperties 631 * 632 * Binds the source object's properties to the target object. Binding 633 * properties allows two-way read/write for the properties of the source object. 634 * 635 * Example: 636 * <pre> 637 * var obj = { x: 34, y: 100 }; 638 * var foo = {} 639 * 640 * // bind properties of "obj" to "foo" object 641 * Object.bindProperties(foo, obj); 642 * 643 * // now, we can access/write on 'foo' properties 644 * print(foo.x); // prints obj.x which is 34 645 * 646 * // update obj.x via foo.x 647 * foo.x = "hello"; 648 * print(obj.x); // prints "hello" now 649 * 650 * obj.x = 42; // foo.x also becomes 42 651 * print(foo.x); // prints 42 652 * </pre> 653 * <p> 654 * The source object bound can be a ScriptObject or a ScriptOjectMirror. 655 * null or undefined source object results in TypeError being thrown. 656 * </p> 657 * Example: 658 * <pre> 659 * var obj = loadWithNewGlobal({ 660 * name: "test", 661 * script: "obj = { x: 33, y: 'hello' }" 662 * }); 663 * 664 * // bind 'obj's properties to global scope 'this' 665 * Object.bindProperties(this, obj); 666 * print(x); // prints 33 667 * print(y); // prints "hello" 668 * x = Math.PI; // changes obj.x to Math.PI 669 * print(obj.x); // prints Math.PI 670 * </pre> 671 * 672 * Limitations of property binding: 673 * <ul> 674 * <li> Only enumerable, immediate (not proto inherited) properties of the source object are bound. 675 * <li> If the target object already contains a property called "foo", the source's "foo" is skipped (not bound). 676 * <li> Properties added to the source object after binding to the target are not bound. 677 * <li> Property configuration changes on the source object (or on the target) is not propagated. 678 * <li> Delete of property on the target (or the source) is not propagated - 679 * only the property value is set to 'undefined' if the property happens to be a data property. 680 * </ul> 681 * <p> 682 * It is recommended that the bound properties be treated as non-configurable 683 * properties to avoid surprises. 684 * </p> 685 * 686 * @param self self reference 687 * @param target the target object to which the source object's properties are bound 688 * @param source the source object whose properties are bound to the target 689 * @return the target object after property binding 690 */ 691 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 692 public static Object bindProperties(final Object self, final Object target, final Object source) { 693 // target object has to be a ScriptObject 694 final ScriptObject targetObj = Global.checkObject(target); 695 // check null or undefined source object 696 Global.checkObjectCoercible(source); 697 698 if (source instanceof ScriptObject) { 699 final ScriptObject sourceObj = (ScriptObject)source; 700 701 final PropertyMap sourceMap = sourceObj.getMap(); 702 final Property[] properties = sourceMap.getProperties(); 703 //replace the map and blow up everything to objects to work with dual fields :-( 704 705 // filter non-enumerable properties 706 final ArrayList<Property> propList = new ArrayList<>(); 707 for (final Property prop : properties) { 708 if (prop.isEnumerable()) { 709 final Object value = sourceObj.get(prop.getKey()); 710 prop.setType(Object.class); 711 prop.setValue(sourceObj, sourceObj, value, false); 712 propList.add(prop); 713 } 714 } 715 716 if (!propList.isEmpty()) { 717 targetObj.addBoundProperties(sourceObj, propList.toArray(new Property[0])); 718 } 719 } else if (source instanceof ScriptObjectMirror) { 720 // get enumerable, immediate properties of mirror 721 final ScriptObjectMirror mirror = (ScriptObjectMirror)source; 722 final String[] keys = mirror.getOwnKeys(false); 723 if (keys.length == 0) { 724 // nothing to bind 725 return target; 726 } 727 728 // make accessor properties using dynamic invoker getters and setters 729 final AccessorProperty[] props = new AccessorProperty[keys.length]; 730 for (int idx = 0; idx < keys.length; idx++) { 731 props[idx] = createAccessorProperty(keys[idx]); 732 } 733 734 targetObj.addBoundProperties(source, props); 735 } else if (source instanceof StaticClass) { 736 final Class<?> clazz = ((StaticClass)source).getRepresentedClass(); 737 Bootstrap.checkReflectionAccess(clazz, true); 738 bindBeanProperties(targetObj, source, BeansLinker.getReadableStaticPropertyNames(clazz), 739 BeansLinker.getWritableStaticPropertyNames(clazz), BeansLinker.getStaticMethodNames(clazz)); 740 } else { 741 final Class<?> clazz = source.getClass(); 742 Bootstrap.checkReflectionAccess(clazz, false); 743 bindBeanProperties(targetObj, source, BeansLinker.getReadableInstancePropertyNames(clazz), 744 BeansLinker.getWritableInstancePropertyNames(clazz), BeansLinker.getInstanceMethodNames(clazz)); 745 } 746 747 return target; 748 } 749 750 private static AccessorProperty createAccessorProperty(final String name) { 751 final MethodHandle getter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.GET_METHOD_PROPERTY, MIRROR_GETTER_TYPE); 752 final MethodHandle setter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.SET_PROPERTY, MIRROR_SETTER_TYPE); 753 return AccessorProperty.create(name, 0, getter, setter); 754 } 755 756 /** 757 * Binds the source mirror object's properties to the target object. Binding 758 * properties allows two-way read/write for the properties of the source object. 759 * All inherited, enumerable properties are also bound. This method is used to 760 * to make 'with' statement work with ScriptObjectMirror as scope object. 761 * 762 * @param target the target object to which the source object's properties are bound 763 * @param source the source object whose properties are bound to the target 764 * @return the target object after property binding 765 */ 766 public static Object bindAllProperties(final ScriptObject target, final ScriptObjectMirror source) { 767 final Set<String> keys = source.keySet(); 768 // make accessor properties using dynamic invoker getters and setters 769 final AccessorProperty[] props = new AccessorProperty[keys.size()]; 770 int idx = 0; 771 for (final String name : keys) { 772 props[idx] = createAccessorProperty(name); 773 idx++; 774 } 775 776 target.addBoundProperties(source, props); 777 return target; 778 } 779 780 private static void bindBeanProperties(final ScriptObject targetObj, final Object source, 781 final Collection<String> readablePropertyNames, final Collection<String> writablePropertyNames, 782 final Collection<String> methodNames) { 783 final Set<String> propertyNames = new HashSet<>(readablePropertyNames); 784 propertyNames.addAll(writablePropertyNames); 785 786 final Class<?> clazz = source.getClass(); 787 788 final MethodType getterType = MethodType.methodType(Object.class, clazz); 789 final MethodType setterType = MethodType.methodType(Object.class, clazz, Object.class); 790 791 final GuardingDynamicLinker linker = Bootstrap.getBeanLinkerForClass(clazz); 792 793 final List<AccessorProperty> properties = new ArrayList<>(propertyNames.size() + methodNames.size()); 794 for(final String methodName: methodNames) { 795 final MethodHandle method; 796 try { 797 method = getBeanOperation(linker, GET_METHOD, methodName, getterType, source); 798 } catch(final IllegalAccessError e) { 799 // Presumably, this was a caller sensitive method. Ignore it and carry on. 800 continue; 801 } 802 properties.add(AccessorProperty.create(methodName, Property.NOT_WRITABLE, getBoundBeanMethodGetter(source, 803 method), Lookup.EMPTY_SETTER)); 804 } 805 for(final String propertyName: propertyNames) { 806 MethodHandle getter; 807 if(readablePropertyNames.contains(propertyName)) { 808 try { 809 getter = getBeanOperation(linker, GET_PROPERTY, propertyName, getterType, source); 810 } catch(final IllegalAccessError e) { 811 // Presumably, this was a caller sensitive method. Ignore it and carry on. 812 getter = Lookup.EMPTY_GETTER; 813 } 814 } else { 815 getter = Lookup.EMPTY_GETTER; 816 } 817 final boolean isWritable = writablePropertyNames.contains(propertyName); 818 MethodHandle setter; 819 if(isWritable) { 820 try { 821 setter = getBeanOperation(linker, SET_PROPERTY, propertyName, setterType, source); 822 } catch(final IllegalAccessError e) { 823 // Presumably, this was a caller sensitive method. Ignore it and carry on. 824 setter = Lookup.EMPTY_SETTER; 825 } 826 } else { 827 setter = Lookup.EMPTY_SETTER; 828 } 829 if(getter != Lookup.EMPTY_GETTER || setter != Lookup.EMPTY_SETTER) { 830 properties.add(AccessorProperty.create(propertyName, isWritable ? 0 : Property.NOT_WRITABLE, getter, setter)); 831 } 832 } 833 834 targetObj.addBoundProperties(source, properties.toArray(new AccessorProperty[0])); 835 } 836 837 private static MethodHandle getBoundBeanMethodGetter(final Object source, final MethodHandle methodGetter) { 838 try { 839 // NOTE: we're relying on the fact that StandardOperation.GET_METHOD return value is constant for any given method 840 // name and object linked with BeansLinker. (Actually, an even stronger assumption is true: return value is 841 // constant for any given method name and object's class.) 842 return MethodHandles.dropArguments(MethodHandles.constant(Object.class, 843 Bootstrap.bindCallable(methodGetter.invoke(source), source, null)), 0, Object.class); 844 } catch(RuntimeException|Error e) { 845 throw e; 846 } catch(final Throwable t) { 847 throw new RuntimeException(t); 848 } 849 } 850 851 private static MethodHandle getBeanOperation(final GuardingDynamicLinker linker, final Operation operation, 852 final String name, final MethodType methodType, final Object source) { 853 final GuardedInvocation inv; 854 try { 855 inv = NashornBeansLinker.getGuardedInvocation(linker, createLinkRequest(operation.named(name), methodType, source), Bootstrap.getLinkerServices()); 856 assert passesGuard(source, inv.getGuard()); 857 } catch(RuntimeException|Error e) { 858 throw e; 859 } catch(final Throwable t) { 860 throw new RuntimeException(t); 861 } 862 assert inv.getSwitchPoints() == null; // Linkers in Dynalink's beans package don't use switchpoints. 863 // We discard the guard, as all method handles will be bound to a specific object. 864 return inv.getInvocation(); 865 } 866 867 private static boolean passesGuard(final Object obj, final MethodHandle guard) throws Throwable { 868 return guard == null || (boolean)guard.invoke(obj); 869 } 870 871 private static LinkRequest createLinkRequest(final Operation operation, final MethodType methodType, final Object source) { 872 return new SimpleLinkRequest(new CallSiteDescriptor(MethodHandles.publicLookup(), operation, 873 methodType), false, source); 874 } 875 876 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 877 return MH.findStatic(MethodHandles.lookup(), NativeObject.class, name, MH.type(rtype, types)); 878 } 879 }