src/jdk/nashorn/api/scripting/ScriptObjectMirror.java

Thu, 07 Nov 2013 17:26:46 +0530

author
sundar
date
Thu, 07 Nov 2013 17:26:46 +0530
changeset 668
2f07b4234451
parent 665
dcedc753fd09
child 748
a4a1d38f0294
permissions
-rw-r--r--

8027828: ClassCastException when converting return value of a Java method to boolean
Reviewed-by: jlaskey, attila

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

mercurial