Tue, 09 Jul 2013 17:37:46 +0530
8014785: Ability to extend global instance by binding properties of another object
Reviewed-by: attila, hannesw, jlaskey, lagergren
1.1 --- a/src/jdk/nashorn/internal/objects/NativeObject.java Tue Jul 09 13:57:24 2013 +0200 1.2 +++ b/src/jdk/nashorn/internal/objects/NativeObject.java Tue Jul 09 17:37:46 2013 +0530 1.3 @@ -27,18 +27,24 @@ 1.4 1.5 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 1.6 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 1.7 + 1.8 +import java.lang.invoke.MethodHandle; 1.9 +import java.util.ArrayList; 1.10 import jdk.nashorn.api.scripting.ScriptObjectMirror; 1.11 import jdk.nashorn.internal.objects.annotations.Attribute; 1.12 import jdk.nashorn.internal.objects.annotations.Constructor; 1.13 import jdk.nashorn.internal.objects.annotations.Function; 1.14 import jdk.nashorn.internal.objects.annotations.ScriptClass; 1.15 import jdk.nashorn.internal.objects.annotations.Where; 1.16 +import jdk.nashorn.internal.runtime.AccessorProperty; 1.17 import jdk.nashorn.internal.runtime.ECMAException; 1.18 import jdk.nashorn.internal.runtime.JSType; 1.19 +import jdk.nashorn.internal.runtime.Property; 1.20 import jdk.nashorn.internal.runtime.PropertyMap; 1.21 import jdk.nashorn.internal.runtime.ScriptFunction; 1.22 import jdk.nashorn.internal.runtime.ScriptObject; 1.23 import jdk.nashorn.internal.runtime.ScriptRuntime; 1.24 +import jdk.nashorn.internal.runtime.linker.Bootstrap; 1.25 import jdk.nashorn.internal.runtime.linker.InvokeByName; 1.26 1.27 /** 1.28 @@ -471,4 +477,114 @@ 1.29 1.30 return false; 1.31 } 1.32 + 1.33 + /** 1.34 + * Nashorn extension: Object.bindProperties 1.35 + * 1.36 + * Binds the source object's properties to the target object. Binding 1.37 + * properties allows two-way read/write for the properties of the source object. 1.38 + * 1.39 + * Example: 1.40 + * <pre> 1.41 + * var obj = { x: 34, y: 100 }; 1.42 + * var foo = {} 1.43 + * 1.44 + * // bind properties of "obj" to "foo" object 1.45 + * Object.bindProperties(foo, obj); 1.46 + * 1.47 + * // now, we can access/write on 'foo' properties 1.48 + * print(foo.x); // prints obj.x which is 34 1.49 + * 1.50 + * // update obj.x via foo.x 1.51 + * foo.x = "hello"; 1.52 + * print(obj.x); // prints "hello" now 1.53 + * 1.54 + * obj.x = 42; // foo.x also becomes 42 1.55 + * print(foo.x); // prints 42 1.56 + * </pre> 1.57 + * <p> 1.58 + * The source object bound can be a ScriptObject or a ScriptOjectMirror. 1.59 + * null or undefined source object results in TypeError being thrown. 1.60 + * </p> 1.61 + * Example: 1.62 + * <pre> 1.63 + * var obj = loadWithNewGlobal({ 1.64 + * name: "test", 1.65 + * script: "obj = { x: 33, y: 'hello' }" 1.66 + * }); 1.67 + * 1.68 + * // bind 'obj's properties to global scope 'this' 1.69 + * Object.bindProperties(this, obj); 1.70 + * print(x); // prints 33 1.71 + * print(y); // prints "hello" 1.72 + * x = Math.PI; // changes obj.x to Math.PI 1.73 + * print(obj.x); // prints Math.PI 1.74 + * </pre> 1.75 + * 1.76 + * Limitations of property binding: 1.77 + * <ul> 1.78 + * <li> Only enumerable, immediate (not proto inherited) properties of the source object are bound. 1.79 + * <li> If the target object already contains a property called "foo", the source's "foo" is skipped (not bound). 1.80 + * <li> Properties added to the source object after binding to the target are not bound. 1.81 + * <li> Property configuration changes on the source object (or on the target) is not propagated. 1.82 + * <li> Delete of property on the target (or the source) is not propagated - 1.83 + * only the property value is set to 'undefined' if the property happens to be a data property. 1.84 + * </ul> 1.85 + * <p> 1.86 + * It is recommended that the bound properties be treated as non-configurable 1.87 + * properties to avoid surprises. 1.88 + * </p> 1.89 + * 1.90 + * @param self self reference 1.91 + * @param target the target object to which the source object's properties are bound 1.92 + * @param source the source object whose properties are bound to the target 1.93 + * @return the target object after property binding 1.94 + */ 1.95 + @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 1.96 + public static Object bindProperties(final Object self, final Object target, final Object source) { 1.97 + // target object has to be a ScriptObject 1.98 + Global.checkObject(target); 1.99 + // check null or undefined source object 1.100 + Global.checkObjectCoercible(source); 1.101 + 1.102 + final ScriptObject targetObj = (ScriptObject)target; 1.103 + 1.104 + if (source instanceof ScriptObject) { 1.105 + final ScriptObject sourceObj = (ScriptObject)source; 1.106 + final Property[] properties = sourceObj.getMap().getProperties(); 1.107 + 1.108 + // filter non-enumerable properties 1.109 + final ArrayList<Property> propList = new ArrayList<>(); 1.110 + for (Property prop : properties) { 1.111 + if (prop.isEnumerable()) { 1.112 + propList.add(prop); 1.113 + } 1.114 + } 1.115 + 1.116 + if (! propList.isEmpty()) { 1.117 + targetObj.addBoundProperties(sourceObj, propList.toArray(new Property[propList.size()])); 1.118 + } 1.119 + } else if (source instanceof ScriptObjectMirror) { 1.120 + // get enumerable, immediate properties of mirror 1.121 + final ScriptObjectMirror mirror = (ScriptObjectMirror)source; 1.122 + final String[] keys = mirror.getOwnKeys(false); 1.123 + if (keys.length == 0) { 1.124 + // nothing to bind 1.125 + return target; 1.126 + } 1.127 + 1.128 + // make accessor properties using dynamic invoker getters and setters 1.129 + final AccessorProperty[] props = new AccessorProperty[keys.length]; 1.130 + for (int idx = 0; idx < keys.length; idx++) { 1.131 + final String name = keys[idx]; 1.132 + final MethodHandle getter = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getElem:" + name, Object.class, ScriptObjectMirror.class); 1.133 + final MethodHandle setter = Bootstrap.createDynamicInvoker("dyn:setProp|setElem:" + name, Object.class, ScriptObjectMirror.class, Object.class); 1.134 + props[idx] = (AccessorProperty.create(name, 0, getter, setter)); 1.135 + } 1.136 + 1.137 + targetObj.addBoundProperties(source, props); 1.138 + } 1.139 + 1.140 + return target; 1.141 + } 1.142 }
2.1 --- a/src/jdk/nashorn/internal/runtime/AccessorProperty.java Tue Jul 09 13:57:24 2013 +0200 2.2 +++ b/src/jdk/nashorn/internal/runtime/AccessorProperty.java Tue Jul 09 17:37:46 2013 +0530 2.3 @@ -147,9 +147,9 @@ 2.4 * and are thus rebound with that as receiver 2.5 * 2.6 * @param property accessor property to rebind 2.7 - * @param delegate delegate script object to rebind receiver to 2.8 + * @param delegate delegate object to rebind receiver to 2.9 */ 2.10 - public AccessorProperty(final AccessorProperty property, final ScriptObject delegate) { 2.11 + public AccessorProperty(final AccessorProperty property, final Object delegate) { 2.12 super(property); 2.13 2.14 this.primitiveGetter = bindTo(property.primitiveGetter, delegate);
3.1 --- a/src/jdk/nashorn/internal/runtime/Context.java Tue Jul 09 13:57:24 2013 +0200 3.2 +++ b/src/jdk/nashorn/internal/runtime/Context.java Tue Jul 09 17:37:46 2013 +0530 3.3 @@ -199,6 +199,7 @@ 3.4 3.5 private static final ClassLoader myLoader = Context.class.getClassLoader(); 3.6 private static final StructureLoader sharedLoader; 3.7 + private static final AccessControlContext NO_PERMISSIONS_CONTEXT; 3.8 3.9 static { 3.10 sharedLoader = AccessController.doPrivileged(new PrivilegedAction<StructureLoader>() { 3.11 @@ -207,6 +208,7 @@ 3.12 return new StructureLoader(myLoader, null); 3.13 } 3.14 }); 3.15 + NO_PERMISSIONS_CONTEXT = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, new Permissions()) }); 3.16 } 3.17 3.18 /** 3.19 @@ -564,7 +566,7 @@ 3.20 sm.checkPackageAccess(fullName.substring(0, index)); 3.21 return null; 3.22 } 3.23 - }, createNoPermissionsContext()); 3.24 + }, NO_PERMISSIONS_CONTEXT); 3.25 } 3.26 } 3.27 3.28 @@ -707,10 +709,6 @@ 3.29 return (context != null) ? context : Context.getContextTrusted(); 3.30 } 3.31 3.32 - private static AccessControlContext createNoPermissionsContext() { 3.33 - return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, new Permissions()) }); 3.34 - } 3.35 - 3.36 private Object evaluateSource(final Source source, final ScriptObject scope, final ScriptObject thiz) { 3.37 ScriptFunction script = null; 3.38
4.1 --- a/src/jdk/nashorn/internal/runtime/PropertyMap.java Tue Jul 09 13:57:24 2013 +0200 4.2 +++ b/src/jdk/nashorn/internal/runtime/PropertyMap.java Tue Jul 09 17:37:46 2013 +0530 4.3 @@ -260,7 +260,7 @@ 4.4 * 4.5 * @return New {@link PropertyMap} with {@link Property} added. 4.6 */ 4.7 - PropertyMap addPropertyBind(final AccessorProperty property, final ScriptObject bindTo) { 4.8 + PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { 4.9 return addProperty(new AccessorProperty(property, bindTo)); 4.10 } 4.11
5.1 --- a/src/jdk/nashorn/internal/runtime/ScriptObject.java Tue Jul 09 13:57:24 2013 +0200 5.2 +++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java Tue Jul 09 17:37:46 2013 +0530 5.3 @@ -203,9 +203,19 @@ 5.4 * @param source The source object to copy from. 5.5 */ 5.6 public void addBoundProperties(final ScriptObject source) { 5.7 + addBoundProperties(source, source.getMap().getProperties()); 5.8 + } 5.9 + 5.10 + /** 5.11 + * Copy all properties from the array with their receiver bound to the source. 5.12 + * 5.13 + * @param source The source object to copy from. 5.14 + * @param properties The array of properties to copy. 5.15 + */ 5.16 + public void addBoundProperties(final ScriptObject source, final Property[] properties) { 5.17 PropertyMap newMap = this.getMap(); 5.18 5.19 - for (final Property property : source.getMap().getProperties()) { 5.20 + for (final Property property : properties) { 5.21 final String key = property.getKey(); 5.22 5.23 if (newMap.findProperty(key) == null) { 5.24 @@ -222,6 +232,26 @@ 5.25 } 5.26 5.27 /** 5.28 + * Copy all properties from the array with their receiver bound to the source. 5.29 + * 5.30 + * @param source The source object to copy from. 5.31 + * @param properties The collection of accessor properties to copy. 5.32 + */ 5.33 + public void addBoundProperties(final Object source, final AccessorProperty[] properties) { 5.34 + PropertyMap newMap = this.getMap(); 5.35 + 5.36 + for (final AccessorProperty property : properties) { 5.37 + final String key = property.getKey(); 5.38 + 5.39 + if (newMap.findProperty(key) == null) { 5.40 + newMap = newMap.addPropertyBind(property, source); 5.41 + } 5.42 + } 5.43 + 5.44 + this.setMap(newMap); 5.45 + } 5.46 + 5.47 + /** 5.48 * Bind the method handle to the specified receiver, while preserving its original type (it will just ignore the 5.49 * first argument in lieu of the bound argument). 5.50 * @param methodHandle Method handle to bind to.
6.1 --- a/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java Tue Jul 09 13:57:24 2013 +0200 6.2 +++ b/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java Tue Jul 09 17:37:46 2013 +0530 6.3 @@ -83,7 +83,7 @@ 6.4 */ 6.5 public InvokeByName(final String name, final Class<?> targetClass, final Class<?> rtype, final Class<?>... ptypes) { 6.6 this.name = name; 6.7 - getter = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getItem:" + name, Object.class, targetClass); 6.8 + getter = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getElem:" + name, Object.class, targetClass); 6.9 6.10 final Class<?>[] finalPtypes; 6.11 final int plength = ptypes.length;
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/test/script/basic/JDK-8014785.js Tue Jul 09 17:37:46 2013 +0530 7.3 @@ -0,0 +1,62 @@ 7.4 +/* 7.5 + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 7.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 7.7 + * 7.8 + * This code is free software; you can redistribute it and/or modify it 7.9 + * under the terms of the GNU General Public License version 2 only, as 7.10 + * published by the Free Software Foundation. 7.11 + * 7.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 7.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 7.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 7.15 + * version 2 for more details (a copy is included in the LICENSE file that 7.16 + * accompanied this code). 7.17 + * 7.18 + * You should have received a copy of the GNU General Public License version 7.19 + * 2 along with this work; if not, write to the Free Software Foundation, 7.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 7.21 + * 7.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 7.23 + * or visit www.oracle.com if you need additional information or have any 7.24 + * questions. 7.25 + */ 7.26 + 7.27 +/** 7.28 + * JDK-8014785: Ability to extend global instance by binding properties of another object 7.29 + * 7.30 + * @test 7.31 + * @run 7.32 + */ 7.33 + 7.34 +var obj = { x: 34, y: 100 }; 7.35 +var foo = {} 7.36 + 7.37 +// bind properties of "obj" to "foo" obj 7.38 +Object.bindProperties(foo, obj); 7.39 + 7.40 +// now we can access/write on foo properties 7.41 +print("foo.x = " + foo.x); // prints obj.x which is 34 7.42 + 7.43 +// update obj.x via foo.x 7.44 +foo.x = "hello"; 7.45 +print("obj.x = " + obj.x); // prints "hello" now 7.46 + 7.47 +obj.x = 42; // foo.x also becomes 42 7.48 +print("obj.x = " + obj.x); // prints 42 7.49 +print("foo.x = " + foo.x); // prints 42 7.50 + 7.51 +// now bind a mirror object to an object 7.52 +var obj = loadWithNewGlobal({ 7.53 + name: "test", 7.54 + script: "obj = { x: 33, y: 'hello' }" 7.55 +}); 7.56 + 7.57 +Object.bindProperties(this, obj); 7.58 +print("x = " + x); // prints 33 7.59 +print("y = " + y); // prints "hello" 7.60 + 7.61 +x = Math.PI; // changes obj.x to Math.PI 7.62 +print("obj.x = " +obj.x); // prints Math.PI 7.63 + 7.64 +obj.y = 32; 7.65 +print("y = " + y); // should print 32
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 8.2 +++ b/test/script/basic/JDK-8014785.js.EXPECTED Tue Jul 09 17:37:46 2013 +0530 8.3 @@ -0,0 +1,8 @@ 8.4 +foo.x = 34 8.5 +obj.x = hello 8.6 +obj.x = 42 8.7 +foo.x = 42 8.8 +x = 33 8.9 +y = hello 8.10 +obj.x = 3.141592653589793 8.11 +y = 32