8014785: Ability to extend global instance by binding properties of another object

Tue, 09 Jul 2013 17:37:46 +0530

author
sundar
date
Tue, 09 Jul 2013 17:37:46 +0530
changeset 423
7538a59ca241
parent 422
d3f4e5dea634
child 424
d480015ab732

8014785: Ability to extend global instance by binding properties of another object
Reviewed-by: attila, hannesw, jlaskey, lagergren

src/jdk/nashorn/internal/objects/NativeObject.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/AccessorProperty.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/Context.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/PropertyMap.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/ScriptObject.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/linker/InvokeByName.java file | annotate | diff | comparison | revisions
test/script/basic/JDK-8014785.js file | annotate | diff | comparison | revisions
test/script/basic/JDK-8014785.js.EXPECTED file | annotate | diff | comparison | revisions
     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

mercurial